summaryrefslogtreecommitdiffhomepage
path: root/packages/web/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/web/src')
-rw-r--r--packages/web/src/components/Header.astro2
-rw-r--r--packages/web/src/components/Share.tsx103
-rw-r--r--packages/web/src/components/share.module.css80
3 files changed, 146 insertions, 39 deletions
diff --git a/packages/web/src/components/Header.astro b/packages/web/src/components/Header.astro
index 1b531db61..b3e393826 100644
--- a/packages/web/src/components/Header.astro
+++ b/packages/web/src/components/Header.astro
@@ -10,7 +10,7 @@ const path = Astro.url.pathname;
const links = config.social || [];
---
-{ path.startsWith("/share")
+{ path.startsWith("/s")
? <div class="header sl-flex">
<div class="title-wrapper sl-flex">
<SiteTitle {...Astro.props} />
diff --git a/packages/web/src/components/Share.tsx b/packages/web/src/components/Share.tsx
index 378ff62b9..e91048047 100644
--- a/packages/web/src/components/Share.tsx
+++ b/packages/web/src/components/Share.tsx
@@ -23,11 +23,13 @@ import {
import {
IconFolder,
IconCpuChip,
+ IconHashtag,
IconSparkles,
IconGlobeAlt,
IconDocument,
IconQueueList,
IconUserCircle,
+ IconCheckCircle,
IconChevronDown,
IconCommandLine,
IconChevronRight,
@@ -504,13 +506,52 @@ function ToolFooter(props: { time: number }) {
)
}
+interface AnchorProps extends JSX.HTMLAttributes<HTMLDivElement> {
+ id: string
+}
+function AnchorIcon(props: AnchorProps) {
+ const [local, rest] = splitProps(props, ["id", "children"])
+ const [copied, setCopied] = createSignal(false)
+
+ return (
+ <div
+ {...rest}
+ data-element-anchor
+ title="Link to this message"
+ data-status={copied() ? "copied" : ""}
+ >
+ <a
+ href={`#${local.id}`}
+ onClick={e => {
+ e.preventDefault()
+
+ const anchor = e.currentTarget
+ const hash = anchor.getAttribute("href") || ""
+ const { origin, pathname, search } = window.location
+
+ navigator.clipboard
+ .writeText(`${origin}${pathname}${search}${hash}`)
+ .catch((err) => console.error("Copy failed", err))
+
+ setCopied(true)
+ setTimeout(() => setCopied(false), 3000)
+ }}
+ >
+ {local.children}
+ <IconHashtag width={18} height={18} />
+ <IconCheckCircle width={18} height={18} />
+ </a>
+ <span data-element-tooltip>Copied!</span>
+ </div>
+ )
+}
+
export default function Share(props: {
id: string
api: string
info: Session.Info
messages: Record<string, Message.Info>
}) {
- console.log(props.info)
let hasScrolled = false
const id = props.id
@@ -844,9 +885,9 @@ export default function Share(props: {
data-part-type="user-text"
>
<div data-section="decoration">
- <a href={`#${anchor()}`} title="Message">
+ <AnchorIcon id={anchor()}>
<IconUserCircle width={18} height={18} />
- </a>
+ </AnchorIcon>
<div></div>
</div>
<div data-section="content">
@@ -874,9 +915,9 @@ export default function Share(props: {
data-part-type="ai-text"
>
<div data-section="decoration">
- <a href={`#${anchor()}`} title="AI response">
+ <AnchorIcon id={anchor()}>
<IconSparkles width={18} height={18} />
- </a>
+ </AnchorIcon>
<div></div>
</div>
<div data-section="content">
@@ -922,12 +963,12 @@ export default function Share(props: {
data-part-type="ai-model"
>
<div data-section="decoration">
- <a href={`#${anchor()}`} title="Model">
+ <AnchorIcon id={anchor()}>
<ProviderIcon
size={18}
provider={assistant().providerID}
/>
- </a>
+ </AnchorIcon>
<div></div>
</div>
<div data-section="content">
@@ -981,9 +1022,9 @@ export default function Share(props: {
data-part-type="system-text"
>
<div data-section="decoration">
- <a href={`#${anchor()}`} title="System message">
+ <AnchorIcon id={anchor()}>
<IconCpuChip width={18} height={18} />
- </a>
+ </AnchorIcon>
<div></div>
</div>
<div data-section="content">
@@ -1024,12 +1065,12 @@ export default function Share(props: {
data-part-type="tool-grep"
>
<div data-section="decoration">
- <a href={`#${anchor()}`} title="Grep files">
+ <AnchorIcon id={anchor()}>
<IconDocumentMagnifyingGlass
width={18}
height={18}
/>
- </a>
+ </AnchorIcon>
<div></div>
</div>
<div data-section="content">
@@ -1118,12 +1159,12 @@ export default function Share(props: {
data-part-type="tool-glob"
>
<div data-section="decoration">
- <a href={`#${anchor()}`} title="Glob files">
+ <AnchorIcon id={anchor()}>
<IconMagnifyingGlass
width={18}
height={18}
/>
- </a>
+ </AnchorIcon>
<div></div>
</div>
<div data-section="content">
@@ -1200,12 +1241,12 @@ export default function Share(props: {
data-part-type="tool-list"
>
<div data-section="decoration">
- <a href={`#${anchor()}`} title="List files">
+ <AnchorIcon id={anchor()}>
<IconRectangleStack
width={18}
height={18}
/>
- </a>
+ </AnchorIcon>
<div></div>
</div>
<div data-section="content">
@@ -1267,9 +1308,9 @@ export default function Share(props: {
data-part-type="tool-read"
>
<div data-section="decoration">
- <a href={`#${anchor()}`} title="Read file">
+ <AnchorIcon id={anchor()}>
<IconDocument width={18} height={18} />
- </a>
+ </AnchorIcon>
<div></div>
</div>
<div data-section="content">
@@ -1364,9 +1405,9 @@ export default function Share(props: {
data-part-type="tool-write"
>
<div data-section="decoration">
- <a href={`#${anchor()}`} title="Write file">
+ <AnchorIcon id={anchor()}>
<IconDocumentPlus width={18} height={18} />
- </a>
+ </AnchorIcon>
<div></div>
</div>
<div data-section="content">
@@ -1447,9 +1488,9 @@ export default function Share(props: {
data-part-type="tool-edit"
>
<div data-section="decoration">
- <a href={`#${anchor()}`} title="Edit file">
+ <AnchorIcon id={anchor()}>
<IconPencilSquare width={18} height={18} />
- </a>
+ </AnchorIcon>
<div></div>
</div>
<div data-section="content">
@@ -1506,9 +1547,9 @@ export default function Share(props: {
data-part-type="tool-bash"
>
<div data-section="decoration">
- <a href={`#${anchor()}`} title="Bash command">
+ <AnchorIcon id={anchor()}>
<IconCommandLine width={18} height={18} />
- </a>
+ </AnchorIcon>
<div></div>
</div>
<div data-section="content">
@@ -1554,9 +1595,9 @@ export default function Share(props: {
data-part-type="tool-todo"
>
<div data-section="decoration">
- <a href={`#${anchor()}`} title="Plan">
+ <AnchorIcon id={anchor()}>
<IconQueueList width={18} height={18} />
- </a>
+ </AnchorIcon>
<div></div>
</div>
<div data-section="content">
@@ -1614,9 +1655,9 @@ export default function Share(props: {
data-part-type="tool-fetch"
>
<div data-section="decoration">
- <a href={`#${anchor()}`} title="Web fetch">
+ <AnchorIcon id={anchor()}>
<IconGlobeAlt width={18} height={18} />
- </a>
+ </AnchorIcon>
<div></div>
</div>
<div data-section="content">
@@ -1675,12 +1716,12 @@ export default function Share(props: {
data-part-type="tool-fallback"
>
<div data-section="decoration">
- <a href={`#${anchor()}`} title="Tool call">
+ <AnchorIcon id={anchor()}>
<IconWrenchScrewdriver
width={18}
height={18}
/>
- </a>
+ </AnchorIcon>
<div></div>
</div>
<div data-section="content">
@@ -1749,7 +1790,7 @@ export default function Share(props: {
data-part-type="fallback"
>
<div data-section="decoration">
- <a href={`#${anchor()}`}>
+ <AnchorIcon id={anchor()}>
<Switch
fallback={
<IconWrenchScrewdriver
@@ -1773,7 +1814,7 @@ export default function Share(props: {
<IconUserCircle width={18} height={18} />
</Match>
</Switch>
- </a>
+ </AnchorIcon>
<div></div>
</div>
<div data-section="content">
diff --git a/packages/web/src/components/share.module.css b/packages/web/src/components/share.module.css
index f14d8ba58..c068259ed 100644
--- a/packages/web/src/components/share.module.css
+++ b/packages/web/src/components/share.module.css
@@ -149,14 +149,80 @@
align-items: center;
justify-content: flex-start;
- a:first-child {
- display: block;
- flex: 0 0 auto;
- width: 18px;
- opacity: 0.65;
- svg {
- color: var(--sl-color-text-secondary);
+ [data-element-anchor] {
+ position: relative;
+
+ a:first-child {
display: block;
+ flex: 0 0 auto;
+ width: 18px;
+ opacity: 0.65;
+
+ svg {
+ color: var(--sl-color-text-secondary);
+ display: block;
+
+ &:nth-child(3) {
+ color: var(--sl-color-green-high);
+ }
+ }
+
+ svg:nth-child(2),
+ svg:nth-child(3) {
+ display: none;
+ }
+ &:hover {
+ svg:nth-child(1) {
+ display: none;
+ }
+ svg:nth-child(2) {
+ display: block;
+ }
+ }
+ }
+
+ [data-element-tooltip] {
+ position: absolute;
+ top: 50%;
+ left: calc(100% + 12px);
+ transform: translate(0, -50%);
+ line-height: 1.1;
+ padding: 0.375em 0.5em calc(0.375em + 2px);
+ background: var(--sl-color-white);
+ color: var(--sl-color-text-invert);
+ font-size: 0.6875rem;
+ border-radius: 7px;
+ white-space: nowrap;
+
+ opacity: 0;
+ visibility: hidden;
+
+ &::after {
+ content: "";
+ position: absolute;
+ top: 50%;
+ left: -15px;
+ transform: translateY(-50%);
+ border: 8px solid transparent;
+ border-right-color: var(--sl-color-white);
+ }
+ }
+
+ &[data-status="copied"] {
+ [data-element-tooltip] {
+ opacity: 1;
+ visibility: visible;
+ }
+ a,
+ a:hover {
+ svg:nth-child(1),
+ svg:nth-child(2) {
+ display: none;
+ }
+ svg:nth-child(3) {
+ display: block;
+ }
+ }
}
}