···11+MIT License Copyright (c) 2025 flo-bit
22+33+Permission is hereby granted, free of
44+charge, to any person obtaining a copy of this software and associated
55+documentation files (the "Software"), to deal in the Software without
66+restriction, including without limitation the rights to use, copy, modify, merge,
77+publish, distribute, sublicense, and/or sell copies of the Software, and to
88+permit persons to whom the Software is furnished to do so, subject to the
99+following conditions:
1010+1111+The above copyright notice and this permission notice
1212+(including the next paragraph) shall be included in all copies or substantial
1313+portions of the Software.
1414+1515+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
1616+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1717+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
1818+EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1919+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
2020+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2121+THE SOFTWARE.
+3
README.md
···11+# jazz roomy
22+33+group chat app, exploring jazz and design patterns for group chat, WIP, testing only
···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'
7676+ };
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 };
···11+<script>
22+ import SpaceSelection from '$lib/SpaceSelection.svelte';
33+</script>
44+55+<SpaceSelection />
static/favicon.png
This is a binary file and will not be displayed.
+18
svelte.config.js
···11+import adapter from '@sveltejs/adapter-vercel';
22+import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
33+44+/** @type {import('@sveltejs/kit').Config} */
55+const config = {
66+ // Consult https://svelte.dev/docs/kit/integrations
77+ // for more information about preprocessors
88+ preprocess: vitePreprocess(),
99+1010+ kit: {
1111+ // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
1212+ // If your environment is not supported, or you settled on a specific environment, switch out the adapter.
1313+ // See https://svelte.dev/docs/kit/adapters for more information about adapters.
1414+ adapter: adapter()
1515+ }
1616+};
1717+1818+export default config;
+19
tsconfig.json
···11+{
22+ "extends": "./.svelte-kit/tsconfig.json",
33+ "compilerOptions": {
44+ "allowJs": true,
55+ "checkJs": true,
66+ "esModuleInterop": true,
77+ "forceConsistentCasingInFileNames": true,
88+ "resolveJsonModule": true,
99+ "skipLibCheck": true,
1010+ "sourceMap": true,
1111+ "strict": true,
1212+ "moduleResolution": "bundler"
1313+ }
1414+ // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
1515+ // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
1616+ //
1717+ // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
1818+ // from the referenced tsconfig.json - TypeScript does not merge them in
1919+}
+7
vite.config.ts
···11+import tailwindcss from '@tailwindcss/vite';
22+import { sveltekit } from '@sveltejs/kit/vite';
33+import { defineConfig } from 'vite';
44+55+export default defineConfig({
66+ plugins: [tailwindcss(), sveltekit()]
77+});