···2424 'site.standard.publication',
2525 'site.standard.document',
2626 'xyz.statusphere.status',
2727- 'community.lexicon.calendar.rsvp'
2727+ 'community.lexicon.calendar.rsvp',
2828+ 'community.lexicon.calendar.event'
2829 ],
29303031 // what types of authenticated proxied requests you can make to services
···22 import { Alert, Button, Input, Subheading } from '@foxui/core';
33 import Modal from '$lib/components/modal/Modal.svelte';
44 import type { CreationModalComponentProps } from '../../types';
55+ import { getRecord } from '$lib/atproto/methods';
66+ import type { Did } from '@atcute/lexicons';
5768 const EVENT_COLLECTION = 'community.lexicon.calendar.event';
79···3840 throw new Error('Invalid URL format');
3941 }
40424141- // Validate the event exists by fetching it
4242- const response = await fetch(
4343- `https://smokesignal.events/xrpc/community.lexicon.calendar.GetEvent?repository=${encodeURIComponent(parsed.did)}&record_key=${encodeURIComponent(parsed.rkey)}`
4444- );
4343+ // Validate the event exists by fetching the record directly
4444+ const record = await getRecord({
4545+ did: parsed.did as Did,
4646+ collection: EVENT_COLLECTION,
4747+ rkey: parsed.rkey
4848+ });
45494646- if (!response.ok) {
5050+ if (!record?.value) {
4751 throw new Error('Event not found');
4852 }
4953···5559 errorMessage =
5660 err instanceof Error && err.message === 'Event not found'
5761 ? "Couldn't find that event. Please check the URL and try again."
5858- : 'Invalid URL. Please enter a valid smokesignal.events URL or AT URI.';
6262+ : 'Invalid URL. Please enter a valid event AT URI or smokesignal.events URL.';
5963 return false;
6064 } finally {
6165 isValidating = false;
···7074 }}
7175 class="flex flex-col gap-2"
7276 >
7373- <Subheading>Enter a Smoke Signal event URL</Subheading>
7777+ <Subheading>Enter an event URL</Subheading>
7478 <Input
7579 bind:value={eventUrl}
7676- placeholder="https://smokesignal.events/did:.../..."
8080+ placeholder="at://did:.../community.lexicon.calendar.event/..."
7781 class="mt-4"
7882 />
7983···8286 {/if}
83878488 <p class="text-base-500 dark:text-base-400 mt-2 text-xs">
8585- Paste a URL from <a
8686- href="https://smokesignal.events"
8787- class="text-accent-800 dark:text-accent-300"
8888- target="_blank">smokesignal.events</a
8989- > or an AT URI for a calendar event.
8989+ Paste an AT URI for a calendar event or a smokesignal.events URL.
9090 </p>
91919292 <div class="mt-4 flex justify-end gap-2">
···11+// from https://github.com/Doist/typist/blob/main/src/extensions/rich-text/rich-text-link.ts
22+import { InputRule, markInputRule, markPasteRule, PasteRule } from '@tiptap/core';
33+import { Link } from '@tiptap/extension-link';
44+55+import type { LinkOptions } from '@tiptap/extension-link';
66+77+/**
88+ * The input regex for Markdown links with title support, and multiple quotation marks (required
99+ * in case the `Typography` extension is being included).
1010+ */
1111+const inputRegex = /(?:^|\s)\[([^\]]*)?\]\((\S+)(?: ["“](.+)["”])?\)$/i;
1212+1313+/**
1414+ * The paste regex for Markdown links with title support, and multiple quotation marks (required
1515+ * in case the `Typography` extension is being included).
1616+ */
1717+const pasteRegex = /(?:^|\s)\[([^\]]*)?\]\((\S+)(?: ["“](.+)["”])?\)/gi;
1818+1919+/**
2020+ * Input rule built specifically for the `Link` extension, which ignores the auto-linked URL in
2121+ * parentheses (e.g., `(https://doist.dev)`).
2222+ *
2323+ * @see https://github.com/ueberdosis/tiptap/discussions/1865
2424+ */
2525+function linkInputRule(config: Parameters<typeof markInputRule>[0]) {
2626+ const defaultMarkInputRule = markInputRule(config);
2727+2828+ return new InputRule({
2929+ find: config.find,
3030+ handler(props) {
3131+ const { tr } = props.state;
3232+3333+ defaultMarkInputRule.handler(props);
3434+ tr.setMeta('preventAutolink', true);
3535+ }
3636+ });
3737+}
3838+3939+/**
4040+ * Paste rule built specifically for the `Link` extension, which ignores the auto-linked URL in
4141+ * parentheses (e.g., `(https://doist.dev)`). This extension was inspired from the multiple
4242+ * implementations found in a Tiptap discussion at GitHub.
4343+ *
4444+ * @see https://github.com/ueberdosis/tiptap/discussions/1865
4545+ */
4646+function linkPasteRule(config: Parameters<typeof markPasteRule>[0]) {
4747+ const defaultMarkPasteRule = markPasteRule(config);
4848+4949+ return new PasteRule({
5050+ find: config.find,
5151+ handler(props) {
5252+ const { tr } = props.state;
5353+5454+ defaultMarkPasteRule.handler(props);
5555+ tr.setMeta('preventAutolink', true);
5656+ }
5757+ });
5858+}
5959+6060+/**
6161+ * The options available to customize the `RichTextLink` extension.
6262+ */
6363+type RichTextLinkOptions = LinkOptions;
6464+6565+/**
6666+ * Custom extension that extends the built-in `Link` extension to add additional input/paste rules
6767+ * for converting the Markdown link syntax (i.e. `[Doist](https://doist.com)`) into links, and also
6868+ * adds support for the `title` attribute.
6969+ */
7070+const RichTextLink = Link.extend<RichTextLinkOptions>({
7171+ inclusive: false,
7272+ addOptions() {
7373+ return {
7474+ ...this.parent?.(),
7575+ openOnClick: 'whenNotEditable' as const
7676+ } as RichTextLinkOptions;
7777+ },
7878+ addAttributes() {
7979+ return {
8080+ ...this.parent?.(),
8181+ title: {
8282+ default: null
8383+ }
8484+ };
8585+ },
8686+ addInputRules() {
8787+ return [
8888+ linkInputRule({
8989+ find: inputRegex,
9090+ type: this.type,
9191+9292+ // We need to use `pop()` to remove the last capture groups from the match to
9393+ // satisfy Tiptap's `markPasteRule` expectation of having the content as the last
9494+ // capture group in the match (this makes the attribute order important)
9595+ getAttributes(match) {
9696+ return {
9797+ title: match.pop()?.trim(),
9898+ href: match.pop()?.trim()
9999+ };
100100+ }
101101+ })
102102+ ];
103103+ },
104104+ addPasteRules() {
105105+ return [
106106+ linkPasteRule({
107107+ find: pasteRegex,
108108+ type: this.type,
109109+110110+ // We need to use `pop()` to remove the last capture groups from the match to
111111+ // satisfy Tiptap's `markInputRule` expectation of having the content as the last
112112+ // capture group in the match (this makes the attribute order important)
113113+ getAttributes(match) {
114114+ return {
115115+ title: match.pop()?.trim(),
116116+ href: match.pop()?.trim()
117117+ };
118118+ }
119119+ })
120120+ ];
121121+ }
122122+});
123123+124124+export { RichTextLink };
125125+126126+export type { RichTextLinkOptions };