Skip to content

Commit dde328a

Browse files
committed
improvement(sim-links): quieter link editor; names take shape as you type
1 parent 297fc45 commit dde328a

4 files changed

Lines changed: 29 additions & 16 deletions

File tree

‎apps/docs/content/docs/search/sim-links.mdx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Type `sim`, press **Space**, then type a link name and press **Enter**. Chrome a
2626
## Create a link
2727

2828
1. Open **Sim Links** and select **New link**.
29-
2. Enter the **Destination URL** and a **Name**. Names can use letters, numbers, and `. : @ & ~ - _`.
29+
2. Enter the **Destination URL** and a **Name**. Names use letters, numbers, and `. : @ & ~ - _`; spaces become hyphens as you type.
3030
3. Optionally add a **Description**, then select **Create**.
3131

3232
To share a link, right-click it and select **Copy link**.
@@ -49,8 +49,8 @@ Values fill each `{*}` in order, and the last `{*}` takes the rest. A link with
4949

5050
By default, any member can edit or delete a link. The link's creator and organization admins can change two options under **Access**:
5151

52-
- **Unlisted**: only the creator and admins see the link on the Sim Links page. Anyone with the name can still open it.
53-
- **Restricted**: only the creator and admins can edit or delete the link.
52+
- **Hide from the Sim Links page**: only the creator and admins see the link in the list, marked **Unlisted**. Anyone with the name can still open it.
53+
- **Only you and admins can edit**: only the creator and admins can edit or delete the link, marked **Restricted**.
5454

5555
Changes to links are recorded in your organization's [audit logs](/platform/enterprise/audit-logs).
5656

‎apps/sim/app/o/[organizationId]/links/sim-link-modal.tsx‎

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,18 @@ import {
1818
SIM_LINK_NAME_MAX_LENGTH,
1919
SIM_LINK_VARIABLE,
2020
simLinkFieldsSchema,
21+
toSimLinkName,
2122
} from '@/lib/sim-links/validation'
2223
import { useCreateSimLink, useUpdateSimLink } from '@/hooks/queries/sim-links'
2324

2425
type Field = 'name' | 'url' | 'defaultUrl' | 'description'
2526

27+
const SIM_LINKS_DOCS_URL = 'https://docs.sim.ai/search/sim-links'
28+
29+
function openDocs() {
30+
window.open(SIM_LINKS_DOCS_URL, '_blank', 'noopener,noreferrer')
31+
}
32+
2633
interface SimLinkModalProps {
2734
organizationId: string
2835
onClose: () => void
@@ -101,11 +108,6 @@ export function SimLinkModal({ organizationId, onClose, link, initialName }: Sim
101108
value={draft.url}
102109
onChange={(value) => change('url', value)}
103110
error={errors.url}
104-
hint={
105-
variable
106-
? `Variable link: sim/${draft.name || 'name'}/value fills each ${SIM_LINK_VARIABLE}.`
107-
: `Add ${SIM_LINK_VARIABLE} where a value typed after the name should go.`
108-
}
109111
/>
110112
{variable && (
111113
<ChipModalField
@@ -117,7 +119,7 @@ export function SimLinkModal({ organizationId, onClose, link, initialName }: Sim
117119
value={draft.defaultUrl ?? ''}
118120
onChange={(value) => change('defaultUrl', value || null)}
119121
error={errors.defaultUrl}
120-
hint={`Opens when sim/${draft.name || 'name'} is used without a value.`}
122+
hint={`Opens when no value fills ${SIM_LINK_VARIABLE}.`}
121123
/>
122124
)}
123125
<ChipModalField
@@ -128,9 +130,8 @@ export function SimLinkModal({ organizationId, onClose, link, initialName }: Sim
128130
placeholder='handbook'
129131
maxLength={SIM_LINK_NAME_MAX_LENGTH}
130132
value={draft.name}
131-
onChange={(value) => change('name', value)}
133+
onChange={(value) => change('name', toSimLinkName(value))}
132134
error={errors.name ?? (nameTaken ? mutation.error?.message : undefined)}
133-
hint='Letters, numbers, and . : @ & ~ - _. Case and punctuation are ignored, so sim/Q4-goals is sim/q4goals.'
134135
/>
135136
<ChipModalField
136137
type='input'
@@ -146,9 +147,7 @@ export function SimLinkModal({ organizationId, onClose, link, initialName }: Sim
146147
type='custom'
147148
title='Access'
148149
hint={
149-
canManage
150-
? 'Anyone in your organization can open this link.'
151-
: 'Only the creator or an organization admin can change access.'
150+
canManage ? undefined : 'Only the creator or an organization admin can change access.'
152151
}
153152
>
154153
{(aria) => (
@@ -162,7 +161,7 @@ export function SimLinkModal({ organizationId, onClose, link, initialName }: Sim
162161
onCheckedChange={(checked) => change('unlisted', checked === true)}
163162
/>
164163
<span className='text-[var(--text-primary)] text-small'>
165-
Hide from the Sim Links page for everyone except you and admins
164+
Hide from the Sim Links page
166165
</span>
167166
</label>
168167
<label htmlFor={restrictEditId} className='flex cursor-pointer items-center gap-2'>
@@ -174,7 +173,7 @@ export function SimLinkModal({ organizationId, onClose, link, initialName }: Sim
174173
onCheckedChange={(checked) => change('restrictEdit', checked === true)}
175174
/>
176175
<span className='text-[var(--text-primary)] text-small'>
177-
Restrict editing to you and admins
176+
Only you and admins can edit
178177
</span>
179178
</label>
180179
</div>
@@ -184,6 +183,7 @@ export function SimLinkModal({ organizationId, onClose, link, initialName }: Sim
184183
</ChipModalBody>
185184
<ChipModalFooter
186185
onCancel={onClose}
186+
secondaryActions={[{ label: 'Learn more', onClick: openDocs }]}
187187
primaryAction={{
188188
label: mutation.isPending ? pendingLabel : actionLabel,
189189
disabled: mutation.isPending,

‎apps/sim/app/o/[organizationId]/links/sim-links.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export function SimLinks({ organizationId }: SimLinksProps) {
8989
const loaded = useMemo(() => links.data?.pages.flatMap((page) => page.links) ?? [], [links.data])
9090
const linksById = useMemo(() => new Map(loaded.map((link) => [link.id, link])), [loaded])
9191
const menuLink = menuLinkId ? linksById.get(menuLinkId) : undefined
92+
if (menuLinkId && !menuLink) setMenuLinkId(null)
9293

9394
const rows: ResourceRow[] = useMemo(
9495
() =>

‎apps/sim/lib/sim-links/validation.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { z } from 'zod'
22

33
const NAME_PATTERN = /^[A-Za-z0-9.:@&~_-]+$/
44
const NAME_PUNCTUATION = /[.:@&~_-]/g
5+
const NAME_DISALLOWED = /[^A-Za-z0-9.:@&~_-]/g
56
export const SIM_LINK_NAME_MAX_LENGTH = 64
67
export const SIM_LINK_DESCRIPTION_MAX_LENGTH = 500
78
const URL_MAX_LENGTH = 2048
@@ -18,6 +19,17 @@ export function normalizeSimLinkName(name: string): string {
1819
return name.toLowerCase().replace(NAME_PUNCTUATION, '')
1920
}
2021

22+
/**
23+
* Shapes typed or pasted text into a name as it is entered: a leading `sim/` is dropped, spaces
24+
* and slashes become hyphens, and any other character a name can't hold is removed.
25+
*/
26+
export function toSimLinkName(input: string): string {
27+
return input
28+
.replace(/^sim\//i, '')
29+
.replace(/[\s/]+/g, '-')
30+
.replace(NAME_DISALLOWED, '')
31+
}
32+
2133
export function isVariableSimLinkUrl(url: string): boolean {
2234
return url.includes(SIM_LINK_VARIABLE)
2335
}

0 commit comments

Comments
 (0)