feat: quote message selections
This commit is contained in:
@@ -16,6 +16,7 @@ const SCROLL_SCOPE = "session"
|
|||||||
const SCROLL_SENTINEL_MARGIN_PX = 48
|
const SCROLL_SENTINEL_MARGIN_PX = 48
|
||||||
const USER_SCROLL_INTENT_WINDOW_MS = 600
|
const USER_SCROLL_INTENT_WINDOW_MS = 600
|
||||||
const SCROLL_INTENT_KEYS = new Set(["ArrowUp", "ArrowDown", "PageUp", "PageDown", "Home", "End", " ", "Spacebar"])
|
const SCROLL_INTENT_KEYS = new Set(["ArrowUp", "ArrowDown", "PageUp", "PageDown", "Home", "End", " ", "Spacebar"])
|
||||||
|
const QUOTE_SELECTION_MAX_LENGTH = 2000
|
||||||
const codeNomadLogo = new URL("../images/CodeNomad-Icon.png", import.meta.url).href
|
const codeNomadLogo = new URL("../images/CodeNomad-Icon.png", import.meta.url).href
|
||||||
|
|
||||||
function formatTokens(tokens: number): string {
|
function formatTokens(tokens: number): string {
|
||||||
@@ -32,6 +33,7 @@ export interface MessageSectionProps {
|
|||||||
showSidebarToggle?: boolean
|
showSidebarToggle?: boolean
|
||||||
onSidebarToggle?: () => void
|
onSidebarToggle?: () => void
|
||||||
forceCompactStatusLayout?: boolean
|
forceCompactStatusLayout?: boolean
|
||||||
|
onQuoteSelection?: (text: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MessageSection(props: MessageSectionProps) {
|
export default function MessageSection(props: MessageSectionProps) {
|
||||||
@@ -137,10 +139,14 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
const [showScrollBottomButton, setShowScrollBottomButton] = createSignal(false)
|
const [showScrollBottomButton, setShowScrollBottomButton] = createSignal(false)
|
||||||
const [topSentinelVisible, setTopSentinelVisible] = createSignal(true)
|
const [topSentinelVisible, setTopSentinelVisible] = createSignal(true)
|
||||||
const [bottomSentinelVisible, setBottomSentinelVisible] = createSignal(true)
|
const [bottomSentinelVisible, setBottomSentinelVisible] = createSignal(true)
|
||||||
|
const [quoteSelection, setQuoteSelection] = createSignal<{ text: string; top: number; left: number } | null>(null)
|
||||||
|
|
||||||
let containerRef: HTMLDivElement | undefined
|
let containerRef: HTMLDivElement | undefined
|
||||||
|
let shellRef: HTMLDivElement | undefined
|
||||||
let pendingScrollFrame: number | null = null
|
let pendingScrollFrame: number | null = null
|
||||||
|
|
||||||
let pendingAnchorScroll: number | null = null
|
let pendingAnchorScroll: number | null = null
|
||||||
|
|
||||||
let pendingScrollPersist: number | null = null
|
let pendingScrollPersist: number | null = null
|
||||||
let userScrollIntentUntil = 0
|
let userScrollIntentUntil = 0
|
||||||
let detachScrollIntentListeners: (() => void) | undefined
|
let detachScrollIntentListeners: (() => void) | undefined
|
||||||
@@ -185,9 +191,20 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
containerRef = element || undefined
|
containerRef = element || undefined
|
||||||
setScrollElement(containerRef)
|
setScrollElement(containerRef)
|
||||||
attachScrollIntentListeners(containerRef)
|
attachScrollIntentListeners(containerRef)
|
||||||
|
if (!containerRef) {
|
||||||
|
clearQuoteSelection()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setShellElement(element: HTMLDivElement | null) {
|
||||||
|
shellRef = element || undefined
|
||||||
|
if (!shellRef) {
|
||||||
|
clearQuoteSelection()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateScrollIndicatorsFromVisibility() {
|
function updateScrollIndicatorsFromVisibility() {
|
||||||
|
|
||||||
const hasItems = messageIds().length > 0
|
const hasItems = messageIds().length > 0
|
||||||
setShowScrollBottomButton(hasItems && !bottomSentinelVisible())
|
setShowScrollBottomButton(hasItems && !bottomSentinelVisible())
|
||||||
setShowScrollTopButton(hasItems && !topSentinelVisible())
|
setShowScrollTopButton(hasItems && !topSentinelVisible())
|
||||||
@@ -237,7 +254,74 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearQuoteSelection() {
|
||||||
|
setQuoteSelection(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSelectionWithinStream(range: Range | null) {
|
||||||
|
if (!range || !containerRef) return false
|
||||||
|
const node = range.commonAncestorContainer
|
||||||
|
if (!node) return false
|
||||||
|
return containerRef.contains(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateQuoteSelectionFromSelection() {
|
||||||
|
if (!props.onQuoteSelection || typeof window === "undefined") {
|
||||||
|
clearQuoteSelection()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
|
||||||
|
clearQuoteSelection()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const range = selection.getRangeAt(0)
|
||||||
|
if (!isSelectionWithinStream(range)) {
|
||||||
|
clearQuoteSelection()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const shell = shellRef
|
||||||
|
if (!shell) {
|
||||||
|
clearQuoteSelection()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const rawText = selection.toString().trim()
|
||||||
|
if (!rawText) {
|
||||||
|
clearQuoteSelection()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const limited =
|
||||||
|
rawText.length > QUOTE_SELECTION_MAX_LENGTH ? rawText.slice(0, QUOTE_SELECTION_MAX_LENGTH).trimEnd() : rawText
|
||||||
|
if (!limited) {
|
||||||
|
clearQuoteSelection()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const rects = range.getClientRects()
|
||||||
|
const anchorRect = rects.length > 0 ? rects[0] : range.getBoundingClientRect()
|
||||||
|
const shellRect = shell.getBoundingClientRect()
|
||||||
|
const relativeTop = Math.max(anchorRect.top - shellRect.top - 40, 8)
|
||||||
|
const maxLeft = Math.max(shell.clientWidth - 180, 8)
|
||||||
|
const relativeLeft = Math.min(Math.max(anchorRect.left - shellRect.left, 8), maxLeft)
|
||||||
|
setQuoteSelection({ text: limited, top: relativeTop, left: relativeLeft })
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleStreamMouseUp() {
|
||||||
|
updateQuoteSelectionFromSelection()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleQuoteSelectionRequest() {
|
||||||
|
const info = quoteSelection()
|
||||||
|
if (!info || !props.onQuoteSelection) return
|
||||||
|
props.onQuoteSelection(info.text)
|
||||||
|
clearQuoteSelection()
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
const selection = window.getSelection()
|
||||||
|
selection?.removeAllRanges()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleContentRendered() {
|
function handleContentRendered() {
|
||||||
|
|
||||||
scheduleAnchorScroll()
|
scheduleAnchorScroll()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,21 +344,53 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearQuoteSelection()
|
||||||
scheduleScrollPersist()
|
scheduleScrollPersist()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (props.registerScrollToBottom) {
|
if (props.registerScrollToBottom) {
|
||||||
props.registerScrollToBottom(() => scrollToBottom(true))
|
props.registerScrollToBottom(() => scrollToBottom(true))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!props.onQuoteSelection) {
|
||||||
|
clearQuoteSelection()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (typeof document === "undefined") return
|
||||||
|
const handleSelectionChange = () => updateQuoteSelectionFromSelection()
|
||||||
|
const handlePointerDown = (event: PointerEvent) => {
|
||||||
|
if (!shellRef) return
|
||||||
|
if (!shellRef.contains(event.target as Node)) {
|
||||||
|
clearQuoteSelection()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener("selectionchange", handleSelectionChange)
|
||||||
|
document.addEventListener("pointerdown", handlePointerDown)
|
||||||
|
onCleanup(() => {
|
||||||
|
document.removeEventListener("selectionchange", handleSelectionChange)
|
||||||
|
document.removeEventListener("pointerdown", handlePointerDown)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (props.loading) {
|
||||||
|
clearQuoteSelection()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const target = containerRef
|
const target = containerRef
|
||||||
const loading = props.loading
|
const loading = props.loading
|
||||||
if (!target || loading || hasRestoredScroll) return
|
if (!target || loading || hasRestoredScroll) return
|
||||||
|
|
||||||
|
|
||||||
scrollCache.restore(target, {
|
scrollCache.restore(target, {
|
||||||
onApplied: (snapshot) => {
|
onApplied: (snapshot) => {
|
||||||
if (snapshot) {
|
if (snapshot) {
|
||||||
@@ -407,6 +523,7 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
if (containerRef) {
|
if (containerRef) {
|
||||||
scrollCache.persist(containerRef, { atBottomOffset: SCROLL_SENTINEL_MARGIN_PX })
|
scrollCache.persist(containerRef, { atBottomOffset: SCROLL_SENTINEL_MARGIN_PX })
|
||||||
}
|
}
|
||||||
|
clearQuoteSelection()
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -423,8 +540,8 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div class={`message-layout${hasTimelineSegments() ? " message-layout--with-timeline" : ""}`}>
|
<div class={`message-layout${hasTimelineSegments() ? " message-layout--with-timeline" : ""}`}>
|
||||||
<div class="message-stream-shell">
|
<div class="message-stream-shell" ref={setShellElement}>
|
||||||
<div class="message-stream" ref={setContainerRef} onScroll={handleScroll}>
|
<div class="message-stream" ref={setContainerRef} onScroll={handleScroll} onMouseUp={handleStreamMouseUp}>
|
||||||
<div ref={setTopSentinel} aria-hidden="true" style={{ height: "1px" }} />
|
<div ref={setTopSentinel} aria-hidden="true" style={{ height: "1px" }} />
|
||||||
<Show when={!props.loading && messageIds().length === 0}>
|
<Show when={!props.loading && messageIds().length === 0}>
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
@@ -494,6 +611,19 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
<Show when={quoteSelection()}>
|
||||||
|
{(selection) => (
|
||||||
|
<div
|
||||||
|
class="message-quote-popover"
|
||||||
|
style={{ top: `${selection().top}px`, left: `${selection().left}px` }}
|
||||||
|
>
|
||||||
|
<button type="button" class="message-quote-button" onClick={handleQuoteSelectionRequest}>
|
||||||
|
Add to prompt
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={hasTimelineSegments()}>
|
<Show when={hasTimelineSegments()}>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ interface PromptInputProps {
|
|||||||
escapeInDebounce?: boolean
|
escapeInDebounce?: boolean
|
||||||
isSessionBusy?: boolean
|
isSessionBusy?: boolean
|
||||||
onAbortSession?: () => Promise<void>
|
onAbortSession?: () => Promise<void>
|
||||||
|
registerQuoteHandler?: (handler: (text: string) => void) => void | (() => void)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PromptInput(props: PromptInputProps) {
|
export default function PromptInput(props: PromptInputProps) {
|
||||||
@@ -42,6 +43,7 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
const [pasteCount, setPasteCount] = createSignal(0)
|
const [pasteCount, setPasteCount] = createSignal(0)
|
||||||
const [imageCount, setImageCount] = createSignal(0)
|
const [imageCount, setImageCount] = createSignal(0)
|
||||||
const [mode, setMode] = createSignal<"normal" | "shell">("normal")
|
const [mode, setMode] = createSignal<"normal" | "shell">("normal")
|
||||||
|
const QUOTE_INSERT_MAX_LENGTH = 2000
|
||||||
let textareaRef: HTMLTextAreaElement | undefined
|
let textareaRef: HTMLTextAreaElement | undefined
|
||||||
let containerRef: HTMLDivElement | undefined
|
let containerRef: HTMLDivElement | undefined
|
||||||
|
|
||||||
@@ -51,6 +53,16 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
const attachments = () => getAttachments(props.instanceId, props.sessionId)
|
const attachments = () => getAttachments(props.instanceId, props.sessionId)
|
||||||
const instanceAgents = () => agents().get(props.instanceId) || []
|
const instanceAgents = () => agents().get(props.instanceId) || []
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!props.registerQuoteHandler) return
|
||||||
|
const cleanup = props.registerQuoteHandler((text) => insertQuotedSelection(text))
|
||||||
|
onCleanup(() => {
|
||||||
|
if (typeof cleanup === "function") {
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const setPrompt = (value: string) => {
|
const setPrompt = (value: string) => {
|
||||||
setPromptInternal(value)
|
setPromptInternal(value)
|
||||||
setSessionDraftPrompt(props.instanceId, props.sessionId, value)
|
setSessionDraftPrompt(props.instanceId, props.sessionId, value)
|
||||||
@@ -869,6 +881,45 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
textareaRef?.focus()
|
textareaRef?.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function insertQuotedSelection(rawText: string) {
|
||||||
|
const normalized = (rawText ?? "").replace(/\r/g, "").trim()
|
||||||
|
if (!normalized) return
|
||||||
|
const limited =
|
||||||
|
normalized.length > QUOTE_INSERT_MAX_LENGTH ? normalized.slice(0, QUOTE_INSERT_MAX_LENGTH).trimEnd() : normalized
|
||||||
|
const lines = limited
|
||||||
|
.split(/\n/)
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter((line) => line.length > 0)
|
||||||
|
if (lines.length === 0) return
|
||||||
|
|
||||||
|
const blockquote = lines.map((line) => `> ${line}`).join("\n")
|
||||||
|
if (!blockquote) return
|
||||||
|
|
||||||
|
const textarea = textareaRef
|
||||||
|
const current = prompt()
|
||||||
|
const start = textarea ? textarea.selectionStart : current.length
|
||||||
|
const end = textarea ? textarea.selectionEnd : current.length
|
||||||
|
const before = current.substring(0, start)
|
||||||
|
const after = current.substring(end)
|
||||||
|
const needsLeading = before.length > 0 && !before.endsWith("\n") ? "\n" : ""
|
||||||
|
const insertion = `${needsLeading}${blockquote}\n\n`
|
||||||
|
const nextValue = before + insertion + after
|
||||||
|
|
||||||
|
setPrompt(nextValue)
|
||||||
|
setHistoryIndex(-1)
|
||||||
|
setHistoryDraft(null)
|
||||||
|
setShowPicker(false)
|
||||||
|
setAtPosition(null)
|
||||||
|
|
||||||
|
if (textarea) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const cursor = before.length + insertion.length
|
||||||
|
textarea.focus()
|
||||||
|
textarea.setSelectionRange(cursor, cursor)
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const canStop = () => Boolean(props.isSessionBusy && props.onAbortSession)
|
const canStop = () => Boolean(props.isSessionBusy && props.onAbortSession)
|
||||||
|
|
||||||
const hasHistory = () => history().length > 0
|
const hasHistory = () => history().length > 0
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export const SessionView: Component<SessionViewProps> = (props) => {
|
|||||||
return getSessionBusyStatus(props.instanceId, currentSession.id)
|
return getSessionBusyStatus(props.instanceId, currentSession.id)
|
||||||
})
|
})
|
||||||
let scrollToBottomHandle: (() => void) | undefined
|
let scrollToBottomHandle: (() => void) | undefined
|
||||||
|
let quoteHandler: ((text: string) => void) | null = null
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const currentSession = session()
|
const currentSession = session()
|
||||||
@@ -45,6 +46,21 @@ export const SessionView: Component<SessionViewProps> = (props) => {
|
|||||||
loadMessages(props.instanceId, currentSession.id).catch((error) => log.error("Failed to load messages", error))
|
loadMessages(props.instanceId, currentSession.id).catch((error) => log.error("Failed to load messages", error))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function registerQuoteHandler(handler: (text: string) => void) {
|
||||||
|
quoteHandler = handler
|
||||||
|
return () => {
|
||||||
|
if (quoteHandler === handler) {
|
||||||
|
quoteHandler = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleQuoteSelection(text: string) {
|
||||||
|
if (quoteHandler) {
|
||||||
|
quoteHandler(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSendMessage(prompt: string, attachments: Attachment[]) {
|
async function handleSendMessage(prompt: string, attachments: Attachment[]) {
|
||||||
|
|
||||||
@@ -183,6 +199,7 @@ export const SessionView: Component<SessionViewProps> = (props) => {
|
|||||||
showSidebarToggle={props.showSidebarToggle}
|
showSidebarToggle={props.showSidebarToggle}
|
||||||
onSidebarToggle={props.onSidebarToggle}
|
onSidebarToggle={props.onSidebarToggle}
|
||||||
forceCompactStatusLayout={props.forceCompactStatusLayout}
|
forceCompactStatusLayout={props.forceCompactStatusLayout}
|
||||||
|
onQuoteSelection={handleQuoteSelection}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
@@ -195,6 +212,7 @@ export const SessionView: Component<SessionViewProps> = (props) => {
|
|||||||
escapeInDebounce={props.escapeInDebounce}
|
escapeInDebounce={props.escapeInDebounce}
|
||||||
isSessionBusy={sessionBusy()}
|
isSessionBusy={sessionBusy()}
|
||||||
onAbortSession={handleAbortSession}
|
onAbortSession={handleAbortSession}
|
||||||
|
registerQuoteHandler={registerQuoteHandler}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -228,3 +228,35 @@
|
|||||||
font-size: var(--font-size-lg);
|
font-size: var(--font-size-lg);
|
||||||
color: var(--accent-primary);
|
color: var(--accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-quote-popover {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 5;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-quote-button {
|
||||||
|
pointer-events: auto;
|
||||||
|
@apply inline-flex items-center gap-2;
|
||||||
|
padding: 0.35rem 0.85rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 9999px;
|
||||||
|
border: 1px solid var(--list-item-highlight-border);
|
||||||
|
background-color: var(--list-item-highlight-bg-solid);
|
||||||
|
color: var(--text-primary);
|
||||||
|
box-shadow: var(--panel-shadow, 0 4px 16px rgba(0, 0, 0, 0.2));
|
||||||
|
transition: background-color 0.2s ease, color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-quote-button:hover {
|
||||||
|
background-color: var(--surface-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-quote-button:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 2px var(--surface-base), 0 0 0 4px var(--accent-primary);
|
||||||
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
--session-status-permission-fg: #c2410c;
|
--session-status-permission-fg: #c2410c;
|
||||||
--session-status-permission-bg: rgba(251, 191, 36, 0.25);
|
--session-status-permission-bg: rgba(251, 191, 36, 0.25);
|
||||||
--list-item-highlight-bg: rgba(0, 102, 255, 0.1);
|
--list-item-highlight-bg: rgba(0, 102, 255, 0.1);
|
||||||
|
--list-item-highlight-bg-solid: #e5f0ff;
|
||||||
--list-item-highlight-border: rgba(0, 102, 255, 0.25);
|
--list-item-highlight-border: rgba(0, 102, 255, 0.25);
|
||||||
--attachment-chip-bg: rgba(0, 102, 255, 0.1);
|
--attachment-chip-bg: rgba(0, 102, 255, 0.1);
|
||||||
--attachment-chip-text: #0066ff;
|
--attachment-chip-text: #0066ff;
|
||||||
@@ -192,8 +193,10 @@
|
|||||||
--session-status-idle-bg: rgba(74, 222, 128, 0.22);
|
--session-status-idle-bg: rgba(74, 222, 128, 0.22);
|
||||||
--session-status-permission-fg: #fbbf24;
|
--session-status-permission-fg: #fbbf24;
|
||||||
--session-status-permission-bg: rgba(251, 191, 36, 0.35);
|
--session-status-permission-bg: rgba(251, 191, 36, 0.35);
|
||||||
--list-item-highlight-bg: rgba(0, 128, 255, 0.2);
|
--list-item-highlight-bg: rgba(0, 128, 255, 0.2);
|
||||||
--list-item-highlight-border: rgba(0, 128, 255, 0.4);
|
--list-item-highlight-bg-solid: #15324e;
|
||||||
|
--list-item-highlight-border: rgba(0, 128, 255, 0.4);
|
||||||
|
|
||||||
--attachment-chip-bg: rgba(0, 128, 255, 0.1);
|
--attachment-chip-bg: rgba(0, 128, 255, 0.1);
|
||||||
--attachment-chip-text: #0080ff;
|
--attachment-chip-text: #0080ff;
|
||||||
--attachment-chip-ring: rgba(0, 128, 255, 0.2);
|
--attachment-chip-ring: rgba(0, 128, 255, 0.2);
|
||||||
@@ -345,6 +348,7 @@
|
|||||||
--session-status-permission-fg: #fbbf24;
|
--session-status-permission-fg: #fbbf24;
|
||||||
--session-status-permission-bg: rgba(251, 191, 36, 0.35);
|
--session-status-permission-bg: rgba(251, 191, 36, 0.35);
|
||||||
--list-item-highlight-bg: rgba(0, 128, 255, 0.2);
|
--list-item-highlight-bg: rgba(0, 128, 255, 0.2);
|
||||||
|
--list-item-highlight-bg-solid: #15324e;
|
||||||
--list-item-highlight-border: rgba(0, 128, 255, 0.4);
|
--list-item-highlight-border: rgba(0, 128, 255, 0.4);
|
||||||
--attachment-chip-bg: rgba(0, 128, 255, 0.1);
|
--attachment-chip-bg: rgba(0, 128, 255, 0.1);
|
||||||
--attachment-chip-text: #0080ff;
|
--attachment-chip-text: #0080ff;
|
||||||
|
|||||||
Reference in New Issue
Block a user