feat: add quote mode options
This commit is contained in:
@@ -33,7 +33,7 @@ export interface MessageSectionProps {
|
|||||||
showSidebarToggle?: boolean
|
showSidebarToggle?: boolean
|
||||||
onSidebarToggle?: () => void
|
onSidebarToggle?: () => void
|
||||||
forceCompactStatusLayout?: boolean
|
forceCompactStatusLayout?: boolean
|
||||||
onQuoteSelection?: (text: string) => void
|
onQuoteSelection?: (text: string, mode: "quote" | "code") => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MessageSection(props: MessageSectionProps) {
|
export default function MessageSection(props: MessageSectionProps) {
|
||||||
@@ -309,10 +309,10 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
updateQuoteSelectionFromSelection()
|
updateQuoteSelectionFromSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleQuoteSelectionRequest() {
|
function handleQuoteSelectionRequest(mode: "quote" | "code") {
|
||||||
const info = quoteSelection()
|
const info = quoteSelection()
|
||||||
if (!info || !props.onQuoteSelection) return
|
if (!info || !props.onQuoteSelection) return
|
||||||
props.onQuoteSelection(info.text)
|
props.onQuoteSelection(info.text, mode)
|
||||||
clearQuoteSelection()
|
clearQuoteSelection()
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
const selection = window.getSelection()
|
const selection = window.getSelection()
|
||||||
@@ -618,9 +618,14 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
class="message-quote-popover"
|
class="message-quote-popover"
|
||||||
style={{ top: `${selection().top}px`, left: `${selection().left}px` }}
|
style={{ top: `${selection().top}px`, left: `${selection().left}px` }}
|
||||||
>
|
>
|
||||||
<button type="button" class="message-quote-button" onClick={handleQuoteSelectionRequest}>
|
<div class="message-quote-button-group">
|
||||||
Add to prompt
|
<button type="button" class="message-quote-button" onClick={() => handleQuoteSelectionRequest("quote")}>
|
||||||
</button>
|
Add as quote
|
||||||
|
</button>
|
||||||
|
<button type="button" class="message-quote-button" onClick={() => handleQuoteSelectionRequest("code")}>
|
||||||
|
Add as code
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -25,7 +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)
|
registerQuoteHandler?: (handler: (text: string, mode: "quote" | "code") => void) => void | (() => void)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PromptInput(props: PromptInputProps) {
|
export default function PromptInput(props: PromptInputProps) {
|
||||||
@@ -43,7 +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
|
const SELECTION_INSERT_MAX_LENGTH = 2000
|
||||||
let textareaRef: HTMLTextAreaElement | undefined
|
let textareaRef: HTMLTextAreaElement | undefined
|
||||||
let containerRef: HTMLDivElement | undefined
|
let containerRef: HTMLDivElement | undefined
|
||||||
|
|
||||||
@@ -55,7 +55,13 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!props.registerQuoteHandler) return
|
if (!props.registerQuoteHandler) return
|
||||||
const cleanup = props.registerQuoteHandler((text) => insertQuotedSelection(text))
|
const cleanup = props.registerQuoteHandler((text, mode) => {
|
||||||
|
if (mode === "code") {
|
||||||
|
insertCodeSelection(text)
|
||||||
|
} else {
|
||||||
|
insertQuotedSelection(text)
|
||||||
|
}
|
||||||
|
})
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
if (typeof cleanup === "function") {
|
if (typeof cleanup === "function") {
|
||||||
cleanup()
|
cleanup()
|
||||||
@@ -881,20 +887,7 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
textareaRef?.focus()
|
textareaRef?.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertQuotedSelection(rawText: string) {
|
function insertBlockContent(block: 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 textarea = textareaRef
|
||||||
const current = prompt()
|
const current = prompt()
|
||||||
const start = textarea ? textarea.selectionStart : current.length
|
const start = textarea ? textarea.selectionStart : current.length
|
||||||
@@ -902,7 +895,7 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
const before = current.substring(0, start)
|
const before = current.substring(0, start)
|
||||||
const after = current.substring(end)
|
const after = current.substring(end)
|
||||||
const needsLeading = before.length > 0 && !before.endsWith("\n") ? "\n" : ""
|
const needsLeading = before.length > 0 && !before.endsWith("\n") ? "\n" : ""
|
||||||
const insertion = `${needsLeading}${blockquote}\n\n`
|
const insertion = `${needsLeading}${block}`
|
||||||
const nextValue = before + insertion + after
|
const nextValue = before + insertion + after
|
||||||
|
|
||||||
setPrompt(nextValue)
|
setPrompt(nextValue)
|
||||||
@@ -920,6 +913,38 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function insertQuotedSelection(rawText: string) {
|
||||||
|
const normalized = (rawText ?? "").replace(/\r/g, "").trim()
|
||||||
|
if (!normalized) return
|
||||||
|
const limited =
|
||||||
|
normalized.length > SELECTION_INSERT_MAX_LENGTH
|
||||||
|
? normalized.slice(0, SELECTION_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
|
||||||
|
|
||||||
|
insertBlockContent(`${blockquote}\n\n`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertCodeSelection(rawText: string) {
|
||||||
|
const normalized = (rawText ?? "").replace(/\r/g, "")
|
||||||
|
const limited =
|
||||||
|
normalized.length > SELECTION_INSERT_MAX_LENGTH
|
||||||
|
? normalized.slice(0, SELECTION_INSERT_MAX_LENGTH)
|
||||||
|
: normalized
|
||||||
|
const trimmed = limited.replace(/^\n+/, "").replace(/\n+$/, "")
|
||||||
|
if (!trimmed) return
|
||||||
|
|
||||||
|
const block = "```\n" + trimmed + "\n```\n\n"
|
||||||
|
insertBlockContent(block)
|
||||||
|
}
|
||||||
|
|
||||||
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,7 +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
|
let quoteHandler: ((text: string, mode: "quote" | "code") => void) | null = null
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const currentSession = session()
|
const currentSession = session()
|
||||||
@@ -47,7 +47,7 @@ export const SessionView: Component<SessionViewProps> = (props) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function registerQuoteHandler(handler: (text: string) => void) {
|
function registerQuoteHandler(handler: (text: string, mode: "quote" | "code") => void) {
|
||||||
quoteHandler = handler
|
quoteHandler = handler
|
||||||
return () => {
|
return () => {
|
||||||
if (quoteHandler === handler) {
|
if (quoteHandler === handler) {
|
||||||
@@ -56,9 +56,9 @@ export const SessionView: Component<SessionViewProps> = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleQuoteSelection(text: string) {
|
function handleQuoteSelection(text: string, mode: "quote" | "code") {
|
||||||
if (quoteHandler) {
|
if (quoteHandler) {
|
||||||
quoteHandler(text)
|
quoteHandler(text, mode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -238,25 +238,37 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-quote-button {
|
.message-quote-button-group {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
@apply inline-flex items-center gap-2;
|
display: inline-flex;
|
||||||
padding: 0.35rem 0.85rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: 500;
|
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
border: 1px solid var(--list-item-highlight-border);
|
border: 1px solid var(--list-item-highlight-border);
|
||||||
background-color: var(--list-item-highlight-bg-solid);
|
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));
|
box-shadow: var(--panel-shadow, 0 4px 16px rgba(0, 0, 0, 0.2));
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-quote-button {
|
||||||
|
pointer-events: auto;
|
||||||
|
@apply inline-flex items-center justify-center;
|
||||||
|
padding: 0.35rem 0.9rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 500;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--text-primary);
|
||||||
transition: background-color 0.2s ease, color 0.2s ease;
|
transition: background-color 0.2s ease, color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-quote-button + .message-quote-button {
|
||||||
|
border-left: 1px solid var(--list-item-highlight-border);
|
||||||
|
}
|
||||||
|
|
||||||
.message-quote-button:hover {
|
.message-quote-button:hover {
|
||||||
background-color: var(--surface-hover);
|
background-color: var(--surface-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-quote-button:focus-visible {
|
.message-quote-button:focus-visible {
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: 0 0 0 2px var(--surface-base), 0 0 0 4px var(--accent-primary);
|
box-shadow: inset 0 0 0 2px var(--accent-primary);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user