Merge branch 'main' into skip-e2e

This commit is contained in:
Darshan
2025-04-03 13:46:31 +05:30
committed by GitHub
58 changed files with 487 additions and 2356 deletions

1
global.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
declare module 'reodotdev';

View File

@@ -42,9 +42,8 @@
"@melt-ui/pp": "^0.3.2", "@melt-ui/pp": "^0.3.2",
"@melt-ui/svelte": "^0.86.5", "@melt-ui/svelte": "^0.86.5",
"@playwright/test": "^1.50.0", "@playwright/test": "^1.50.0",
"@sentry/sveltekit": "^9.10.1", "@sveltejs/adapter-node": "^5.2.12",
"@sveltejs/adapter-node": "^4.0.1", "@sveltejs/enhanced-img": "^0.4.4",
"@sveltejs/enhanced-img": "^0.3.9",
"@sveltejs/kit": "^2.20.2", "@sveltejs/kit": "^2.20.2",
"@sveltejs/vite-plugin-svelte": "^5.0.3", "@sveltejs/vite-plugin-svelte": "^5.0.3",
"@tailwindcss/postcss": "^4.0.17", "@tailwindcss/postcss": "^4.0.17",
@@ -81,6 +80,7 @@
"prettier-plugin-svelte": "^3.3.3", "prettier-plugin-svelte": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.11", "prettier-plugin-tailwindcss": "^0.6.11",
"remeda": "^2.20.0", "remeda": "^2.20.0",
"reodotdev": "^1.0.0",
"sass": "^1.83.4", "sass": "^1.83.4",
"svelte": "^5.25.6", "svelte": "^5.25.6",
"svelte-check": "^4.0.0", "svelte-check": "^4.0.0",
@@ -96,12 +96,11 @@
"vite-plugin-dynamic-import": "^1.6.0", "vite-plugin-dynamic-import": "^1.6.0",
"vite-plugin-image-optimizer": "^1.1.8", "vite-plugin-image-optimizer": "^1.1.8",
"vite-plugin-manifest-sri": "^0.2.0", "vite-plugin-manifest-sri": "^0.2.0",
"vitest": "^1.6.0" "vitest": "^3.1.1"
}, },
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [
"@parcel/watcher", "@parcel/watcher",
"@sentry/cli",
"core-js", "core-js",
"esbuild", "esbuild",
"sharp", "sharp",

2121
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +0,0 @@
import { dev } from '$app/environment';
import { SENTRY_DSN } from '$lib/constants';
import * as Sentry from '@sentry/sveltekit';
import { handleErrorWithSentry } from '@sentry/sveltekit';
Sentry.init({
enabled: !dev,
dsn: SENTRY_DSN,
allowUrls: [/appwrite\.io/],
tracesSampleRate: 1.0,
// This sets the sample rate to be 10%. You may want this to be 100% while
// in development and sample at a lower rate in production
replaysSessionSampleRate: 0,
// If the entire session is not sampled, use the below sample rate to sample
// sessions when an error occurs.
replaysOnErrorSampleRate: 0
});
// If you have a custom error handler, pass it to `handleErrorWithSentry`
export const handleError = handleErrorWithSentry();

View File

@@ -1,135 +0,0 @@
import * as Sentry from '@sentry/sveltekit';
import type { Handle } from '@sveltejs/kit';
import redirects from './redirects.json';
import { sequence } from '@sveltejs/kit/hooks';
import { BANNER_KEY, SENTRY_DSN } from '$lib/constants';
import { dev } from '$app/environment';
Sentry.init({
enabled: !dev,
dsn: SENTRY_DSN,
tracesSampleRate: 1,
allowUrls: [/appwrite\.io/]
});
const redirectMap = new Map(redirects.map(({ link, redirect }) => [link, redirect]));
const redirecter: Handle = async ({ event, resolve }) => {
const currentPath = event.url.pathname;
if (redirectMap.has(currentPath)) {
return new Response(null, {
status: 308,
headers: {
location: redirectMap.get(currentPath) ?? ''
}
});
}
return await resolve(event);
};
const securityheaders: Handle = async ({ event, resolve }) => {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
(event.locals as { nonce: string }).nonce = nonce;
const response = await resolve(event, {
transformPageChunk: ({ html }) => {
return html.replace(/%sveltekit.nonce%/g, nonce);
}
});
// `true` if deployed via Coolify.
const isPreview = !!process.env.COOLIFY_FQDN;
// COOLIFY_FQDN already includes `http`.
const previewDomain = isPreview ? `${process.env.COOLIFY_FQDN}` : null;
const join = (arr: string[]) => arr.join(' ');
const cspDirectives: Record<string, string> = {
'default-src': "'self'",
'script-src': join([
"'self'",
'blob:',
"'unsafe-inline'",
"'unsafe-eval'",
'https://*.posthog.com',
'https://*.plausible.io',
'https://*.reo.dev',
'https://plausible.io',
'https://js.zi-scripts.com',
'https://ws.zoominfo.com'
]),
'style-src': "'self' 'unsafe-inline'",
'img-src': "'self' data: https:",
'font-src': "'self'",
'object-src': "'none'",
'base-uri': "'self'",
'form-action': "'self'",
'frame-ancestors': join(["'self'", 'https://www.youtube.com', 'https://*.vimeo.com']),
'block-all-mixed-content': '',
'upgrade-insecure-requests': '',
'connect-src': join([
"'self'",
'https://*.appwrite.io',
'https://*.appwrite.org',
'https://*.posthog.com',
'https://*.sentry.io',
'https://*.plausible.io',
'https://plausible.io',
'https://*.reo.dev',
'https://js.zi-scripts.com',
'https://aorta.clickagy.com',
'https://hemsync.clickagy.com',
'https://ws.zoominfo.com '
]),
'frame-src': join([
"'self'",
'https://www.youtube.com',
'https://status.appwrite.online',
'https://www.youtube-nocookie.com',
'https://player.vimeo.com',
'https://hemsync.clickagy.com'
])
};
if (isPreview) {
delete cspDirectives['block-all-mixed-content'];
delete cspDirectives['upgrade-insecure-requests'];
['default-src', 'script-src', 'style-src', 'img-src', 'font-src', 'connect-src'].forEach(
(key) => {
cspDirectives[key] += ` ${previewDomain}`;
}
);
}
const cspDirectivesString = Object.entries(cspDirectives)
.map(([key, value]) => `${key} ${value}`.trim())
.join('; ');
// Set security headers
response.headers.set('Content-Security-Policy', cspDirectivesString);
// HTTP Strict Transport Security
// max-age is set to 1 year in seconds
response.headers.set(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains; preload'
);
// X-Content-Type-Options
response.headers.set('X-Content-Type-Options', 'nosniff');
// X-Frame-Options
response.headers.set('X-Frame-Options', 'DENY');
return response;
};
const bannerRewriter: Handle = async ({ event, resolve }) => {
const response = await resolve(event, {
transformPageChunk: ({ html }) => html.replace('%aw_banner_key%', BANNER_KEY)
});
return response;
};
export const handle = sequence(Sentry.sentryHandle(), redirecter, bannerRewriter, securityheaders);
export const handleError = Sentry.handleErrorWithSentry();

View File

@@ -1,8 +1,16 @@
<script lang="ts"> <script lang="ts">
import type { Snippet } from 'svelte';
let carousel: HTMLElement; let carousel: HTMLElement;
export let size: 'default' | 'medium' | 'big' = 'default'; interface Props {
export let gap = 32; size?: 'default' | 'medium' | 'big';
gap?: number;
header: Snippet;
children: Snippet;
}
let { size = 'default', gap = 32, header, children }: Props = $props();
let scroll = 0; let scroll = 0;
function calculateScrollAmount(prev = false) { function calculateScrollAmount(prev = false) {
@@ -32,8 +40,8 @@
}); });
} }
let isEnd = false; let isEnd = $state(false);
let isStart = true; let isStart = $state(true);
function handleScroll() { function handleScroll() {
isStart = carousel.scrollLeft <= 0; isStart = carousel.scrollLeft <= 0;
@@ -43,13 +51,13 @@
<div> <div>
<div class="mt-2 flex flex-wrap items-center"> <div class="mt-2 flex flex-wrap items-center">
<slot name="header" /> {@render header()}
<div class="nav ml-auto flex items-end gap-3"> <div class="nav ml-auto flex items-end gap-3">
<button <button
class="web-icon-button" class="web-icon-button"
aria-label="Move carousel backward" aria-label="Move carousel backward"
disabled={isStart} disabled={isStart}
on:click={prev} onclick={prev}
> >
<span class="web-icon-arrow-left" aria-hidden="true"></span> <span class="web-icon-arrow-left" aria-hidden="true"></span>
</button> </button>
@@ -57,7 +65,7 @@
class="web-icon-button" class="web-icon-button"
aria-label="Move carousel forward" aria-label="Move carousel forward"
disabled={isEnd} disabled={isEnd}
on:click={next} onclick={next}
> >
<span class="web-icon-arrow-right" aria-hidden="true"></span> <span class="web-icon-arrow-right" aria-hidden="true"></span>
</button> </button>
@@ -71,9 +79,9 @@
class:is-big={size === 'big'} class:is-big={size === 'big'}
style:gap="{gap}px" style:gap="{gap}px"
bind:this={carousel} bind:this={carousel}
on:scroll={handleScroll} onscroll={handleScroll}
> >
<slot /> {@render children()}
</ul> </ul>
</div> </div>
</div> </div>

View File

@@ -1,3 +1,7 @@
<!-- @migration-task Error while migrating Svelte code: Cannot use `export let` in runes mode — use `$props()` instead
https://svelte.dev/e/legacy_export_invalid -->
<!-- @migration-task Error while migrating Svelte code: Cannot use `export let` in runes mode — use `$props()` instead
https://svelte.dev/e/legacy_export_invalid -->
<script lang="ts"> <script lang="ts">
import { Select, Tooltip } from '$lib/components'; import { Select, Tooltip } from '$lib/components';
import { getCodeHtml, type Language } from '$lib/utils/code'; import { getCodeHtml, type Language } from '$lib/utils/code';
@@ -26,8 +30,11 @@
Copy: 'Copy', Copy: 'Copy',
Copied: 'Copied!' Copied: 'Copied!'
} as const; } as const;
type CopyStatusType = keyof typeof CopyStatus;
type CopyStatusValue = (typeof CopyStatus)[CopyStatusType];
let copyText: CopyStatusValue = CopyStatus.Copy;
let copyText = CopyStatus.Copy;
async function handleCopy() { async function handleCopy() {
await copy(content); await copy(content);
@@ -78,9 +85,11 @@
aria-label="copy code from code-snippet" aria-label="copy code from code-snippet"
><span class="web-icon-copy" aria-hidden="true"></span></button ><span class="web-icon-copy" aria-hidden="true"></span></button
> >
<svelte:fragment slot="tooltip"> {#snippet tooltip()}
{copyText} <span>
</svelte:fragment> {copyText}
</span>
{/snippet}
</Tooltip> </Tooltip>
</li> </li>
</ul> </ul>

View File

@@ -85,7 +85,9 @@
/> />
</a> </a>
</li> </li>
<svelte:fragment slot="tooltip">{platform.name}</svelte:fragment> {#snippet tooltip()}
{platform.name}
{/snippet}
</Tooltip> </Tooltip>
{/each} {/each}
</ul> </ul>

View File

@@ -72,15 +72,15 @@
); );
</script> </script>
{@render asChild?.({ trigger: $trigger })} {#if asChild}
{@render asChild({ trigger: $trigger })}
{#if !asChild && children} {:else if children}
<span use:melt={$trigger}> <span use:melt={$trigger}>
{@render children()} {@render children()}
</span> </span>
{/if} {/if}
{#if $open && !disabled} {#if tooltip && $open && !disabled}
<div use:melt={$content} class="web-tooltip text-sub-body" transition:fly={flyParams}> <div use:melt={$content} class="web-tooltip text-sub-body" transition:fly={flyParams}>
<div use:melt={$arrow}></div> <div use:melt={$arrow}></div>
{@render tooltip()} {@render tooltip()}

View File

@@ -1,36 +1,41 @@
<!-- @migration-task Error while migrating Svelte code: migrating this component would require adding a `$props` rune but there's already a variable named props.
Rename the variable and try again or migrate by hand. -->
<script lang="ts"> <script lang="ts">
import { classNames } from '$lib/utils/classnames'; import { classNames } from '$lib/utils/classnames';
import type { Snippet } from 'svelte';
import type { HTMLInputAttributes } from 'svelte/elements'; import type { HTMLInputAttributes } from 'svelte/elements';
type $$Props = HTMLInputAttributes & { interface Props extends HTMLInputAttributes {
label?: string; label?: string;
}; icon?: Snippet;
}
export let label: $$Props['label'] = ''; let {
export let type: $$Props['type'] = 'text'; label = '',
export let value: $$Props['value'] = ''; type = 'text',
const { class: classes, name, ...props } = $$restProps; value = $bindable(''),
icon,
class: classes,
name,
...rest
}: Props = $props();
</script> </script>
{#if $$slots.icon} {#if icon}
<label <label
class={classNames( class={classNames(
'focus:border-greyscale-100 bg-greyscale-800 border-greyscale-700 flex items-center gap-1 rounded-lg border px-3 py-2 text-sm font-light transition-colors focus-within:border-white active:shadow-sm active:shadow-black/30', 'focus:border-greyscale-100 bg-greyscale-800 border-greyscale-700 flex items-center gap-1 rounded-lg border px-3 py-2 text-sm font-light transition-colors focus-within:border-white active:shadow-sm active:shadow-black/30',
classes classes
)} )}
> >
<slot name="icon" /> {@render icon?.()}
{#key type} {#key type}
<input <input
{name} {name}
{...{ type }} {...{ type }}
bind:value bind:value
on:input
on:change
on:focus
on:blur
class="w-full border-0 ring-0 outline-none" class="w-full border-0 ring-0 outline-none"
{...props} {...rest}
/> />
{/key} {/key}
</label> </label>
@@ -45,15 +50,11 @@
{name} {name}
{...{ type }} {...{ type }}
bind:value bind:value
on:input
on:change
on:focus
on:blur
class={classNames( class={classNames(
'focus:border-greyscale-100 bg-greyscale-800 border-greyscale-700 mt-2 flex w-full items-center gap-1 rounded-lg border px-3 py-2 text-sm font-light transition-colors focus-within:border-white active:shadow-sm active:shadow-black/30', 'focus:border-greyscale-100 bg-greyscale-800 border-greyscale-700 mt-2 flex w-full items-center gap-1 rounded-lg border px-3 py-2 text-sm font-light transition-colors focus-within:border-white active:shadow-sm active:shadow-black/30',
classes classes
)} )}
{...props} {...rest}
/> />
{/key} {/key}
{/if} {/if}

View File

@@ -1,8 +1,6 @@
export const GITHUB_STARS = '47K'; export const GITHUB_STARS = '47K';
export const GITHUB_REPO_LINK = 'https://github.com/appwrite/appwrite'; export const GITHUB_REPO_LINK = 'https://github.com/appwrite/appwrite';
export const BANNER_KEY: Banners = 'discord-banner-01'; // Change key to force banner to show again export const BANNER_KEY: Banners = 'discord-banner-01'; // Change key to force banner to show again
export const SENTRY_DSN =
'https://27d41dc8bb67b596f137924ab8599e59@o1063647.ingest.us.sentry.io/4507497727000576';
export const BLOG_POSTS_PER_PAGE = 12; export const BLOG_POSTS_PER_PAGE = 12;

View File

@@ -72,7 +72,9 @@
{#if expandable && !$layoutState.showSidenav} {#if expandable && !$layoutState.showSidenav}
<Tooltip placement="right"> <Tooltip placement="right">
<SidebarNavButton groupItem={navGroup} /> <SidebarNavButton groupItem={navGroup} />
<svelte:fragment slot="tooltip">{navGroup.label}</svelte:fragment> {#snippet tooltip()}
<span>{navGroup.label}</span>
{/snippet}
</Tooltip> </Tooltip>
{:else} {:else}
<SidebarNavButton groupItem={navGroup} /> <SidebarNavButton groupItem={navGroup} />
@@ -89,9 +91,9 @@
{#if expandable && !$layoutState.showSidenav} {#if expandable && !$layoutState.showSidenav}
<Tooltip placement="right"> <Tooltip placement="right">
<SidebarNavButton {groupItem} /> <SidebarNavButton {groupItem} />
<svelte:fragment slot="tooltip" {#snippet tooltip()}
>{groupItem.label}</svelte:fragment <span>{groupItem.label}</span>
> {/snippet}
</Tooltip> </Tooltip>
{:else} {:else}
<SidebarNavButton {groupItem} /> <SidebarNavButton {groupItem} />

View File

@@ -35,8 +35,11 @@
Copy: 'Copy', Copy: 'Copy',
Copied: 'Copied!' Copied: 'Copied!'
} as const; } as const;
type CopyStatusType = keyof typeof CopyStatus;
type CopyStatusValue = (typeof CopyStatus)[CopyStatusType];
let copyText = $state<CopyStatusValue>(CopyStatus.Copy);
let copyText = $state(CopyStatus.Copy);
async function handleCopy() { async function handleCopy() {
await copy(toCopy ?? content); await copy(toCopy ?? content);

View File

@@ -52,8 +52,10 @@
Copy: 'Copy', Copy: 'Copy',
Copied: 'Copied!' Copied: 'Copied!'
} as const; } as const;
type CopyStatusType = keyof typeof CopyStatus;
type CopyStatusValue = (typeof CopyStatus)[CopyStatusType];
let copyText = $state(CopyStatus.Copy); let copyText = $state<CopyStatusValue>(CopyStatus.Copy);
async function handleCopy() { async function handleCopy() {
await copy($content); await copy($content);

View File

@@ -1,5 +1,6 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
import { derived, writable } from 'svelte/store'; import { derived, writable } from 'svelte/store';
import { loadReoScript } from 'reodotdev';
export type Theme = 'dark' | 'light' | 'system'; export type Theme = 'dark' | 'light' | 'system';
export const currentTheme = (function () { export const currentTheme = (function () {
@@ -128,6 +129,15 @@
} }
}); });
} }
if (!dev && browser) {
const clientID = '144fa7eaa4904e8';
const reoPromise = loadReoScript({ clientID });
reoPromise.then((Reo: any) => {
Reo.init({ clientID });
});
}
</script> </script>
<svelte:window on:scroll={handleScroll} /> <svelte:window on:scroll={handleScroll} />
@@ -135,6 +145,7 @@
{#if !dev} {#if !dev}
<!--suppress JSUnresolvedLibraryURL --> <!--suppress JSUnresolvedLibraryURL -->
<script defer data-domain="appwrite.io" src="https://plausible.io/js/script.js"></script> <script defer data-domain="appwrite.io" src="https://plausible.io/js/script.js"></script>
<!-- ZoomInfo snippet --> <!-- ZoomInfo snippet -->
<script> <script>
window[ window[
@@ -178,22 +189,6 @@
document.body.appendChild(zi); document.body.appendChild(zi);
}); });
</script> </script>
<!-- Reo.dev -->
<script type="text/javascript">
!(function () {
var e, t, n;
(e = '144fa7eaa4904e8'),
(t = function () {
Reo.init({ clientID: '144fa7eaa4904e8' });
}),
((n = document.createElement('script')).src =
'https://static.reo.dev/' + e + '/reo.js'),
(n.defer = !0),
(n.onload = t),
document.head.appendChild(n);
})();
</script>
{/if} {/if}
<!-- canonical url --> <!-- canonical url -->

View File

@@ -2,10 +2,10 @@
import { setContext } from 'svelte'; import { setContext } from 'svelte';
import type { AuthorData, CategoryData, PostsData } from './content.js'; import type { AuthorData, CategoryData, PostsData } from './content.js';
export let data; let { data, children } = $props();
setContext<PostsData[]>('posts', data.posts); setContext<PostsData[]>('posts', data.posts);
setContext<AuthorData[]>('authors', data.authors); setContext<AuthorData[]>('authors', data.authors);
setContext<CategoryData[]>('categories', data.categories); setContext<CategoryData[]>('categories', data.categories);
</script> </script>
<slot /> {@render children()}

View File

@@ -8,20 +8,20 @@
import { beforeNavigate, goto } from '$app/navigation'; import { beforeNavigate, goto } from '$app/navigation';
import { createDebounce } from '$lib/utils/debounce'; import { createDebounce } from '$lib/utils/debounce';
export let data; let { data } = $props();
const featured = data.featured; const featured = data.featured;
const categories = data.filteredCategories.sort((a, b) => a.name.localeCompare(b.name)); const categories = data.filteredCategories.sort((a, b) => a.name.localeCompare(b.name));
$: isFirstPage = data.currentPage === 1; let isFirstPage = $derived(data.currentPage === 1);
$: isLastPage = data.currentPage === data.totalPages; let isLastPage = $derived(data.currentPage === data.totalPages);
$: currentPageRange = data.navigation || []; let currentPageRange = $derived(data.navigation || []);
let query = ''; let query = $state('');
let isEnd = false; let isEnd = $state(false);
let isStart = true; let isStart = $state(true);
let categoriesElement: HTMLElement; let categoriesElement: HTMLElement;
let articlesHeader: HTMLElement; let articlesHeader: HTMLElement;
@@ -41,7 +41,7 @@
}); });
}); });
let selectedCategory = $page.url.searchParams.get('category') ?? 'Latest'; let selectedCategory = $state($page.url.searchParams.get('category') ?? 'Latest');
const handleSearch = async () => { const handleSearch = async () => {
const searchQuery = query.toLowerCase(); const searchQuery = query.toLowerCase();
@@ -70,7 +70,7 @@
}); });
}; };
$: navigationLink = (pageNumber: number): string => { let navigationLink = $derived((pageNumber: number): string => {
const currentUrl = $page.url; const currentUrl = $page.url;
const url = new URL(`/blog/${pageNumber}`, currentUrl); const url = new URL(`/blog/${pageNumber}`, currentUrl);
@@ -79,7 +79,7 @@
} }
return url.toString(); return url.toString();
}; });
const { debounce, reset } = createDebounce(); const { debounce, reset } = createDebounce();
@@ -271,14 +271,14 @@
> >
<ul <ul
class="categories flex gap-2 overflow-x-auto" class="categories flex gap-2 overflow-x-auto"
on:scroll={handleScroll} onscroll={handleScroll}
bind:this={categoriesElement} bind:this={categoriesElement}
> >
<li class="flex items-center"> <li class="flex items-center">
<button <button
class="web-interactive-tag web-caption-400 cursor-pointer" class="web-interactive-tag web-caption-400 cursor-pointer"
class:is-selected={selectedCategory === 'Latest'} class:is-selected={selectedCategory === 'Latest'}
on:click={() => { onclick={() => {
selectedCategory = 'Latest'; selectedCategory = 'Latest';
handleSearch(); handleSearch();
}} }}
@@ -292,7 +292,7 @@
<button <button
class="web-interactive-tag web-caption-400 cursor-pointer" class="web-interactive-tag web-caption-400 cursor-pointer"
class:is-selected={selectedCategory === category.name} class:is-selected={selectedCategory === category.name}
on:click={() => { onclick={() => {
selectedCategory = category.name; selectedCategory = category.name;
handleSearch(); handleSearch();
}} }}
@@ -351,7 +351,7 @@
<button <button
class="web-button is-secondary" class="web-button is-secondary"
on:click={() => { onclick={() => {
query = ''; query = '';
selectedCategory = 'Latest'; selectedCategory = 'Latest';
handleSearch(); handleSearch();

View File

@@ -30,7 +30,7 @@ export type PostsData = {
slug: string; slug: string;
featured?: boolean; featured?: boolean;
unlisted?: boolean; unlisted?: boolean;
callToAction: { callToAction?: {
heading?: string; heading?: string;
label?: string; label?: string;
url?: string; url?: string;

View File

@@ -9,7 +9,7 @@
import { CHANGELOG_KEY } from '../utils'; import { CHANGELOG_KEY } from '../utils';
import { TITLE_SUFFIX } from '$routes/titles'; import { TITLE_SUFFIX } from '$routes/titles';
export let data; let { data } = $props();
const seo = { const seo = {
title: 'Changelog' + TITLE_SUFFIX, title: 'Changelog' + TITLE_SUFFIX,
@@ -55,15 +55,14 @@
<li> <li>
<div class="web-dot"></div> <div class="web-dot"></div>
<ChangelogEntry {entry}> <ChangelogEntry {entry}>
<svelte:component this={entry.component} /> <entry.component />
</ChangelogEntry> </ChangelogEntry>
</li> </li>
{/each} {/each}
</ul> </ul>
{#if data.nextPage} {#if data.nextPage}
<button class="web-button is-secondary" on:click={loadMore} <button class="web-button is-secondary" onclick={loadMore}>Load more</button
>Load more</button
> >
{/if} {/if}
</div> </div>

View File

@@ -9,7 +9,7 @@
import { DEFAULT_DESCRIPTION, DEFAULT_HOST } from '$lib/utils/metadata'; import { DEFAULT_DESCRIPTION, DEFAULT_HOST } from '$lib/utils/metadata';
import { CHANGELOG_TITLE_SUFFIX } from '$routes/titles'; import { CHANGELOG_TITLE_SUFFIX } from '$routes/titles';
export let data; let { data } = $props();
const seo = { const seo = {
title: data.title, title: data.title,
@@ -25,8 +25,11 @@
Copy: 'Copy', Copy: 'Copy',
Copied: 'Copied!' Copied: 'Copied!'
} as const; } as const;
type CopyStatusType = keyof typeof CopyStatus;
type CopyStatusValue = (typeof CopyStatus)[CopyStatusType];
let copyText = $state<CopyStatusValue>(CopyStatus.Copy);
let copyText = CopyStatus.Copy;
async function handleCopy() { async function handleCopy() {
const blogPostUrl = encodeURI(`https://appwrite.io${$page.url.pathname}`); const blogPostUrl = encodeURI(`https://appwrite.io${$page.url.pathname}`);
@@ -126,11 +129,11 @@
</button> </button>
{/if} {/if}
<svelte:fragment slot="tooltip"> {#snippet tooltip()}
{sharingOption.type === 'copy' {sharingOption.type === 'copy'
? copyText ? copyText
: `Share on ${sharingOption.label}`} : `Share on ${sharingOption.label}`}
</svelte:fragment> {/snippet}
</Tooltip> </Tooltip>
</li> </li>
{/each} {/each}

View File

@@ -1,4 +1,4 @@
<script lang="ts" context="module"> <script lang="ts" module>
export const events: EventCardProps[] = [ export const events: EventCardProps[] = [
{ {
href: 'https://discord.com/events/564160730845151244/1279026334496067669/1286356126924800000', href: 'https://discord.com/events/564160730845151244/1279026334496067669/1286356126924800000',
@@ -43,6 +43,8 @@
</script> </script>
<script lang="ts"> <script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { Carousel } from '$lib/components'; import { Carousel } from '$lib/components';
import FloatingHeads from '$lib/components/FloatingHeads.svelte'; import FloatingHeads from '$lib/components/FloatingHeads.svelte';
import FooterNav from '$lib/components/FooterNav.svelte'; import FooterNav from '$lib/components/FooterNav.svelte';
@@ -59,7 +61,7 @@
import type { ProjectCardProps } from './ProjectCard.svelte'; import type { ProjectCardProps } from './ProjectCard.svelte';
import ProjectCard from './ProjectCard.svelte'; import ProjectCard from './ProjectCard.svelte';
export let data; let { data } = $props();
const projects: ProjectCardProps[] = [ const projects: ProjectCardProps[] = [
{ {
@@ -103,11 +105,11 @@
{ metric: '800+', description: 'Contributors' } { metric: '800+', description: 'Contributors' }
]; ];
let name = ''; let name = $state('');
let email = ''; let email = $state('');
let submitted = false; let submitted = $state(false);
let error: string | undefined; let error: string | undefined = $state();
let submitting = false; let submitting = $state(false);
async function submit() { async function submit() {
submitting = true; submitting = true;
@@ -423,9 +425,9 @@
<div class="web-big-padding-section-level-2"> <div class="web-big-padding-section-level-2">
<section class="web-u-sep-block-start web-u-padding-block-start-64 container"> <section class="web-u-sep-block-start web-u-padding-block-start-64 container">
<Carousel size="big"> <Carousel size="big">
<svelte:fragment slot="header"> {#snippet header()}
<h4 class="text-label text-primary">Upcoming Events</h4> <h4 class="text-label text-primary">Upcoming Events</h4>
</svelte:fragment> {/snippet}
{#each events as event} {#each events as event}
<li> <li>
<EventCard <EventCard
@@ -630,7 +632,10 @@
{:else} {:else}
<form <form
method="post" method="post"
on:submit|preventDefault={submit} onsubmit={(e) => {
e.preventDefault();
submit();
}}
class="flex flex-col gap-4" class="flex flex-col gap-4"
> >
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">

View File

@@ -106,6 +106,7 @@ If running in production, it might be easier to use a 3rd party SMTP server as i
| `_APP_STORAGE_S3_SECRET` | **version >= 0.13.0** AWS S3 storage secret key. Required when the storage adapter is set to S3. You can get your secret key from your AWS console. | | `_APP_STORAGE_S3_SECRET` | **version >= 0.13.0** AWS S3 storage secret key. Required when the storage adapter is set to S3. You can get your secret key from your AWS console. |
| `_APP_STORAGE_S3_REGION` | **version >= 0.13.0** AWS S3 storage region. Required when storage adapter is set to S3. You can find your region info for your bucket from AWS console. | | `_APP_STORAGE_S3_REGION` | **version >= 0.13.0** AWS S3 storage region. Required when storage adapter is set to S3. You can find your region info for your bucket from AWS console. |
| `_APP_STORAGE_S3_BUCKET` | **version >= 0.13.0** AWS S3 storage bucket. Required when storage adapter is set to S3. You can create buckets in your AWS console. | | `_APP_STORAGE_S3_BUCKET` | **version >= 0.13.0** AWS S3 storage bucket. Required when storage adapter is set to S3. You can create buckets in your AWS console. |
| `_APP_STORAGE_S3_ENDPOINT` | **version >= 1.7.0** Override the S3 endpoint to use an S3-compatible provider. This should just be the host (without 'https://'). |
| `_APP_STORAGE_DO_SPACES_ACCESS_KEY` | **version >= 0.13.0** DigitalOcean spaces access key. Required when the storage adapter is set to DOSpaces. You can get your access key from your DigitalOcean console. | | `_APP_STORAGE_DO_SPACES_ACCESS_KEY` | **version >= 0.13.0** DigitalOcean spaces access key. Required when the storage adapter is set to DOSpaces. You can get your access key from your DigitalOcean console. |
| `_APP_STORAGE_DO_SPACES_SECRET` | **version >= 0.13.0** DigitalOcean spaces secret key. Required when the storage adapter is set to DOSpaces. You can get your secret key from your DigitalOcean console. | | `_APP_STORAGE_DO_SPACES_SECRET` | **version >= 0.13.0** DigitalOcean spaces secret key. Required when the storage adapter is set to DOSpaces. You can get your secret key from your DigitalOcean console. |
| `_APP_STORAGE_DO_SPACES_REGION` | **version >= 0.13.0** DigitalOcean spaces region. Required when storage adapter is set to DOSpaces. You can find your region info for your space from DigitalOcean console. | | `_APP_STORAGE_DO_SPACES_REGION` | **version >= 0.13.0** DigitalOcean spaces region. Required when storage adapter is set to DOSpaces. You can find your region info for your space from DigitalOcean console. |

View File

@@ -27,13 +27,13 @@
import Response from './(components)/Response.svelte'; import Response from './(components)/Response.svelte';
import RateLimits from './(components)/RateLimits.svelte'; import RateLimits from './(components)/RateLimits.svelte';
export let data; let { data } = $props();
setContext<LayoutContext>('headings', writable({})); setContext<LayoutContext>('headings', writable({}));
const headings = getContext<LayoutContext>('headings'); const headings = getContext<LayoutContext>('headings');
let selected: string | undefined = undefined; let selected: string | undefined = $state(undefined);
headings.subscribe((n) => { headings.subscribe((n) => {
const noVisible = Object.values(n).every((n) => !n.visible); const noVisible = Object.values(n).every((n) => !n.visible);
if (selected && noVisible) { if (selected && noVisible) {
@@ -119,21 +119,22 @@
}); });
// cleaned service description without Markdown links. // cleaned service description without Markdown links.
$: serviceDescription = (data.service?.description ?? '').replace( let serviceDescription = $derived(
/\[([^\]]+)]\([^)]+\)/g, (data.service?.description ?? '').replace(/\[([^\]]+)]\([^)]+\)/g, '$1')
'$1'
); );
// the service description up to the first full stop, providing sufficient information. // the service description up to the first full stop, providing sufficient information.
$: shortenedDescription = serviceDescription.substring(0, serviceDescription.indexOf('.') + 1); let shortenedDescription = $derived(
serviceDescription.substring(0, serviceDescription.indexOf('.') + 1)
);
$: platformBindingForSelect = $page.params.platform as Platform; let platformBindingForSelect = $derived($page.params.platform as Platform);
$: platform = ($preferredPlatform ?? $page.params.platform) as Platform; let platform = $derived(($preferredPlatform ?? $page.params.platform) as Platform);
$: platformType = platform.startsWith('client-') ? 'CLIENT' : 'SERVER'; let platformType = $derived(platform.startsWith('client-') ? 'CLIENT' : 'SERVER');
$: serviceName = serviceMap[data.service?.name]; let serviceName = $derived(serviceMap[data.service?.name]);
$: title = serviceName + API_REFERENCE_TITLE_SUFFIX; let title = $derived(serviceName + API_REFERENCE_TITLE_SUFFIX);
$: description = shortenedDescription; let description = $derived(shortenedDescription);
$: ogImage = DEFAULT_HOST + '/images/open-graph/docs.png'; let ogImage = $derived(DEFAULT_HOST + '/images/open-graph/docs.png');
</script> </script>
<svelte:head> <svelte:head>
@@ -302,7 +303,7 @@
use:clickOutside={() => ($layoutState.showReferences = false)} use:clickOutside={() => ($layoutState.showReferences = false)}
> >
{#if data.methods.length > 0} {#if data.methods.length > 0}
<button class="web-icon-button" id="refOpen" on:click={toggleReferences}> <button class="web-icon-button" id="refOpen" onclick={toggleReferences}>
<span class="icon-menu-alt-4" aria-hidden="true"></span> <span class="icon-menu-alt-4" aria-hidden="true"></span>
</button> </button>
<div class="web-references-menu-content"> <div class="web-references-menu-content">
@@ -310,7 +311,7 @@
class="web-references-menu-header mt-6 flex items-center justify-between gap-4" class="web-references-menu-header mt-6 flex items-center justify-between gap-4"
> >
<h5 class="web-references-menu-title text-micro uppercase">On This Page</h5> <h5 class="web-references-menu-title text-micro uppercase">On This Page</h5>
<button class="web-icon-button" id="refClose" on:click={toggleReferences}> <button class="web-icon-button" id="refClose" onclick={toggleReferences}>
<span class="icon-x" aria-hidden="true"></span> <span class="icon-x" aria-hidden="true"></span>
</button> </button>
</div> </div>

View File

@@ -6,7 +6,7 @@
import Tabs from '$markdoc/tags/Tabs.svelte'; import Tabs from '$markdoc/tags/Tabs.svelte';
import TabsItem from '$markdoc/tags/TabsItem.svelte'; import TabsItem from '$markdoc/tags/TabsItem.svelte';
export let data; let { data } = $props();
</script> </script>
<svelte:head> <svelte:head>

View File

@@ -8,7 +8,7 @@
'Follow a simple tutorial to get started with Appwrite in your preferred framework quickly and easily.'; 'Follow a simple tutorial to get started with Appwrite in your preferred framework quickly and easily.';
const ogImage = DEFAULT_HOST + '/images/open-graph/docs.png'; const ogImage = DEFAULT_HOST + '/images/open-graph/docs.png';
export let data; let { data } = $props();
type MappedTutorial = (typeof data.tutorials)[number]; type MappedTutorial = (typeof data.tutorials)[number];

View File

@@ -55,13 +55,14 @@ export async function load() {
const tutorials = Object.entries( const tutorials = Object.entries(
allTutorials.reduce((acc: { [key: string]: any[] }, item) => { allTutorials.reduce((acc: { [key: string]: any[] }, item) => {
const cat = item.category as string;
// If the category does not exist in the accumulator, initialize it // If the category does not exist in the accumulator, initialize it
if (!acc[item.category]) { if (!acc[cat]) {
acc[item.category] = []; acc[cat] = [];
} }
// Push the current item into the appropriate category // Push the current item into the appropriate category
acc[item.category].push(item); acc[cat].push(item);
return acc; return acc;
}, {}) }, {})

View File

@@ -2,9 +2,9 @@
import { globToTutorial } from '$lib/utils/tutorials.js'; import { globToTutorial } from '$lib/utils/tutorials.js';
import { setContext } from 'svelte'; import { setContext } from 'svelte';
export let data; let { data, children } = $props();
const tutorials = globToTutorial(data); const tutorials = globToTutorial(data);
setContext('tutorials', tutorials); setContext('tutorials', tutorials);
</script> </script>
<slot /> {@render children()}

View File

@@ -2,9 +2,9 @@
import { globToTutorial } from '$lib/utils/tutorials.js'; import { globToTutorial } from '$lib/utils/tutorials.js';
import { setContext } from 'svelte'; import { setContext } from 'svelte';
export let data; let { data, children } = $props();
const tutorials = globToTutorial(data); const tutorials = globToTutorial(data);
setContext('tutorials', tutorials); setContext('tutorials', tutorials);
</script> </script>
<slot /> {@render children()}

View File

@@ -2,9 +2,9 @@
import { globToTutorial } from '$lib/utils/tutorials.js'; import { globToTutorial } from '$lib/utils/tutorials.js';
import { setContext } from 'svelte'; import { setContext } from 'svelte';
export let data; let { data, children } = $props();
const tutorials = globToTutorial(data); const tutorials = globToTutorial(data);
setContext('tutorials', tutorials); setContext('tutorials', tutorials);
</script> </script>
<slot /> {@render children()}

View File

@@ -2,9 +2,9 @@
import { globToTutorial } from '$lib/utils/tutorials.js'; import { globToTutorial } from '$lib/utils/tutorials.js';
import { setContext } from 'svelte'; import { setContext } from 'svelte';
export let data; let { data, children } = $props();
const tutorials = globToTutorial(data); const tutorials = globToTutorial(data);
setContext('tutorials', tutorials); setContext('tutorials', tutorials);
</script> </script>
<slot /> {@render children()}

View File

@@ -2,9 +2,9 @@
import { globToTutorial } from '$lib/utils/tutorials.js'; import { globToTutorial } from '$lib/utils/tutorials.js';
import { setContext } from 'svelte'; import { setContext } from 'svelte';
export let data; let { data, children } = $props();
const tutorials = globToTutorial(data); const tutorials = globToTutorial(data);
setContext('tutorials', tutorials); setContext('tutorials', tutorials);
</script> </script>
<slot /> {@render children()}

View File

@@ -2,9 +2,9 @@
import { globToTutorial } from '$lib/utils/tutorials.js'; import { globToTutorial } from '$lib/utils/tutorials.js';
import { setContext } from 'svelte'; import { setContext } from 'svelte';
export let data; let { data, children } = $props();
const tutorials = globToTutorial(data); const tutorials = globToTutorial(data);
setContext('tutorials', tutorials); setContext('tutorials', tutorials);
</script> </script>
<slot /> {@render children()}

View File

@@ -2,9 +2,9 @@
import { globToTutorial } from '$lib/utils/tutorials.js'; import { globToTutorial } from '$lib/utils/tutorials.js';
import { setContext } from 'svelte'; import { setContext } from 'svelte';
export let data; let { data, children } = $props();
const tutorials = globToTutorial(data); const tutorials = globToTutorial(data);
setContext('tutorials', tutorials); setContext('tutorials', tutorials);
</script> </script>
<slot /> {@render children()}

View File

@@ -2,9 +2,9 @@
import { globToTutorial } from '$lib/utils/tutorials.js'; import { globToTutorial } from '$lib/utils/tutorials.js';
import { setContext } from 'svelte'; import { setContext } from 'svelte';
export let data; let { data, children } = $props();
const tutorials = globToTutorial(data); const tutorials = globToTutorial(data);
setContext('tutorials', tutorials); setContext('tutorials', tutorials);
</script> </script>
<slot /> {@render children()}

View File

@@ -2,9 +2,9 @@
import { globToTutorial } from '$lib/utils/tutorials.js'; import { globToTutorial } from '$lib/utils/tutorials.js';
import { setContext } from 'svelte'; import { setContext } from 'svelte';
export let data; let { data, children } = $props();
const tutorials = globToTutorial(data); const tutorials = globToTutorial(data);
setContext('tutorials', tutorials); setContext('tutorials', tutorials);
</script> </script>
<slot /> {@render children()}

View File

@@ -2,9 +2,9 @@
import { globToTutorial } from '$lib/utils/tutorials.js'; import { globToTutorial } from '$lib/utils/tutorials.js';
import { setContext } from 'svelte'; import { setContext } from 'svelte';
export let data; let { data, children } = $props();
const tutorials = globToTutorial(data); const tutorials = globToTutorial(data);
setContext('tutorials', tutorials); setContext('tutorials', tutorials);
</script> </script>
<slot /> {@render children()}

View File

@@ -2,9 +2,9 @@
import { globToTutorial } from '$lib/utils/tutorials.js'; import { globToTutorial } from '$lib/utils/tutorials.js';
import { setContext } from 'svelte'; import { setContext } from 'svelte';
export let data; let { data, children } = $props();
const tutorials = globToTutorial(data); const tutorials = globToTutorial(data);
setContext('tutorials', tutorials); setContext('tutorials', tutorials);
</script> </script>
<slot /> {@render children()}

View File

@@ -2,9 +2,9 @@
import { globToTutorial } from '$lib/utils/tutorials.js'; import { globToTutorial } from '$lib/utils/tutorials.js';
import { setContext } from 'svelte'; import { setContext } from 'svelte';
export let data; let { data, children } = $props();
const tutorials = globToTutorial(data); const tutorials = globToTutorial(data);
setContext('tutorials', tutorials); setContext('tutorials', tutorials);
</script> </script>
<slot /> {@render children()}

View File

@@ -2,9 +2,9 @@
import { globToTutorial } from '$lib/utils/tutorials.js'; import { globToTutorial } from '$lib/utils/tutorials.js';
import { setContext } from 'svelte'; import { setContext } from 'svelte';
export let data; let { data, children } = $props();
const tutorials = globToTutorial(data); const tutorials = globToTutorial(data);
setContext('tutorials', tutorials); setContext('tutorials', tutorials);
</script> </script>
<slot /> {@render children()}

View File

@@ -2,9 +2,9 @@
import { globToTutorial } from '$lib/utils/tutorials.js'; import { globToTutorial } from '$lib/utils/tutorials.js';
import { setContext } from 'svelte'; import { setContext } from 'svelte';
export let data; let { data, children } = $props();
const tutorials = globToTutorial(data); const tutorials = globToTutorial(data);
setContext('tutorials', tutorials); setContext('tutorials', tutorials);
</script> </script>
<slot /> {@render children()}

View File

@@ -2,9 +2,9 @@
import { globToTutorial } from '$lib/utils/tutorials.js'; import { globToTutorial } from '$lib/utils/tutorials.js';
import { setContext } from 'svelte'; import { setContext } from 'svelte';
export let data; let { data, children } = $props();
const tutorials = globToTutorial(data); const tutorials = globToTutorial(data);
setContext('tutorials', tutorials); setContext('tutorials', tutorials);
</script> </script>
<slot /> {@render children()}

View File

@@ -7,7 +7,7 @@
import TicketPreview from '$routes/init-0/(components)/TicketPreview.svelte'; import TicketPreview from '$routes/init-0/(components)/TicketPreview.svelte';
import Ticket from '../../(components)/Ticket.svelte'; import Ticket from '../../(components)/Ticket.svelte';
export let data; let { data } = $props();
let firstName = data.ticket?.name?.split(/\s/)[0] ?? ''; let firstName = data.ticket?.name?.split(/\s/)[0] ?? '';
const ogImage = `${$page.url.origin}/init-0/tickets/${data.ticket.$id}/og`; const ogImage = `${$page.url.origin}/init-0/tickets/${data.ticket.$id}/og`;
@@ -56,7 +56,7 @@
<a class="web-button" href="/init-0/tickets"> <a class="web-button" href="/init-0/tickets">
<span class="text">Get my Init ticket</span> <span class="text">Get my Init ticket</span>
</a> </a>
<button class="web-button is-secondary" on:click={copy}> <button class="web-button is-secondary" onclick={copy}>
<span class="web-icon-{$copied ? 'check' : 'copy'} text-primary"></span> <span class="web-icon-{$copied ? 'check' : 'copy'} text-primary"></span>
<span class="text">Copy ticket URL</span> <span class="text">Copy ticket URL</span>

View File

@@ -11,23 +11,25 @@
import Form from './form.svelte'; import Form from './form.svelte';
import type { TicketVariant } from '../constants'; import type { TicketVariant } from '../constants';
export let data; let { data } = $props();
let name = data.ticket?.name ?? ''; let name = $state(data.ticket?.name ?? '');
const id = data.ticket?.id ?? 0; const id = data.ticket?.id ?? 0;
let tribe: string | undefined = data.ticket?.tribe ?? undefined; let tribe: string | undefined = $state(data.ticket?.tribe ?? undefined);
let showGitHub = data.ticket?.show_contributions ?? true; let showGitHub = $state(data.ticket?.show_contributions ?? true);
let drawerOpen = false; let drawerOpen = $state(false);
let customizing = false; let customizing = $state(false);
let variant: TicketVariant = data.ticket.variant ?? 'default'; let variant: TicketVariant = $state(data.ticket.variant ?? 'default');
$: modified = !dequal( let modified = $derived(
{ !dequal(
name: data.ticket?.name, {
tribe: data.ticket?.tribe, name: data.ticket?.name,
showGitHub: data.ticket?.show_contributions tribe: data.ticket?.tribe,
}, showGitHub: data.ticket?.show_contributions
{ name, tribe, showGitHub } },
{ name, tribe, showGitHub }
)
); );
async function saveTicket() { async function saveTicket() {
@@ -57,12 +59,14 @@
const ticketUrl = `${$page.url.origin}/init-0/tickets/${data.ticket.$id}`; const ticketUrl = `${$page.url.origin}/init-0/tickets/${data.ticket.$id}`;
const { copied, copy } = createCopy(ticketUrl); const { copied, copy } = createCopy(ticketUrl);
$: twitterText = encodeURIComponent( let twitterText = $derived(
[ encodeURIComponent(
`Join Init and celebrate everything new with @appwrite`, [
``, `Join Init and celebrate everything new with @appwrite`,
`Claim your ticket. ${ticketUrl}` ``,
].join('\n') `Claim your ticket. ${ticketUrl}`
].join('\n')
)
); );
</script> </script>
@@ -78,7 +82,7 @@
<div class="hero"> <div class="hero">
{#if customizing} {#if customizing}
<div style:margin-block-start="0.625rem"> <div style:margin-block-start="0.625rem">
<button class="web-link is-secondary u-cross-center" on:click={goBack}> <button class="web-link is-secondary u-cross-center" onclick={goBack}>
<span class="web-icon-chevron-left" aria-hidden="true"></span> <span class="web-icon-chevron-left" aria-hidden="true"></span>
<span>Back</span> <span>Back</span>
@@ -110,14 +114,14 @@
<div class="info"> <div class="info">
<button <button
on:click={() => (customizing = true)} onclick={() => (customizing = true)}
class="web-button is-full-width u-margin-block-start-32" class="web-button is-full-width u-margin-block-start-32"
> >
<span class="text">Customize ticket</span> <span class="text">Customize ticket</span>
</button> </button>
<div class="u-flex u-cross-center u-gap-16 u-margin-block-start-16"> <div class="u-flex u-cross-center u-gap-16 u-margin-block-start-16">
<button class="web-button is-full-width is-secondary" on:click={copy}> <button class="web-button is-full-width is-secondary" onclick={copy}>
<div class="web-icon-{$copied ? 'check' : 'copy'} text-primary"></div> <div class="web-icon-{$copied ? 'check' : 'copy'} text-primary"></div>
<span class="text">Copy ticket URL</span> <span class="text">Copy ticket URL</span>
</button> </button>
@@ -155,7 +159,7 @@
{#if customizing} {#if customizing}
<div class="drawer" data-state={drawerOpen ? 'open' : 'closed'}> <div class="drawer" data-state={drawerOpen ? 'open' : 'closed'}>
<button on:click={() => (drawerOpen = !drawerOpen)}> <button onclick={() => (drawerOpen = !drawerOpen)}>
<div class="inner"> <div class="inner">
<span class="text-label text-primary">Ticket Editor</span> <span class="text-label text-primary">Ticket Editor</span>
<span class="web-icon-chevron-down"></span> <span class="web-icon-chevron-down"></span>

View File

@@ -29,7 +29,9 @@
> >
<img {src} {alt} /> <img {src} {alt} />
</button> </button>
<svelte:fragment slot="tooltip">{alt}</svelte:fragment> {#snippet tooltip()}
{alt}
{/snippet}
</Tooltip> </Tooltip>
<style lang="scss"> <style lang="scss">

View File

@@ -57,7 +57,9 @@
<Tooltip placement="bottom"> <Tooltip placement="bottom">
<Switch bind:checked={showGitHub} /> <Switch bind:checked={showGitHub} />
<svelte:fragment slot="tooltip">Show GitHub contributions</svelte:fragment> {#snippet tooltip()}
Show GitHub contributions
{/snippet}
</Tooltip> </Tooltip>
</div> </div>
{:else} {:else}

View File

@@ -6,7 +6,7 @@
import { createCopy } from '$lib/utils/copy'; import { createCopy } from '$lib/utils/copy';
import { TicketPreview, Ticket } from '$routes/init/(components)/ticket'; import { TicketPreview, Ticket } from '$routes/init/(components)/ticket';
export let data; let { data } = $props();
let firstName = data.ticket?.name?.split(/\s/)[0] ?? ''; let firstName = data.ticket?.name?.split(/\s/)[0] ?? '';
const ogImage = `${$page.url.origin}/init/tickets/${data.ticket.$id}/og`; const ogImage = `${$page.url.origin}/init/tickets/${data.ticket.$id}/og`;
@@ -52,7 +52,7 @@
<a class="web-button" href="/init/tickets"> <a class="web-button" href="/init/tickets">
<span class="text">Get my ticket</span> <span class="text">Get my ticket</span>
</a> </a>
<button class="web-button is-secondary" on:click={copy}> <button class="web-button is-secondary" onclick={copy}>
<span class="web-icon-{$copied ? 'check' : 'copy'} text-primary"></span> <span class="web-icon-{$copied ? 'check' : 'copy'} text-primary"></span>
<span class="text">Copy ticket URL</span> <span class="text">Copy ticket URL</span>

View File

@@ -10,25 +10,27 @@
TicketDetails TicketDetails
} from '$routes/init/(components)/ticket/index.js'; } from '$routes/init/(components)/ticket/index.js';
export let data; let { data } = $props();
let originalName = data.ticket?.name ?? ''; let originalName = data.ticket?.name ?? '';
let name = originalName; let name = $state(originalName);
let originalTitle = data.ticket?.title ?? ''; let originalTitle = data.ticket?.title ?? '';
let title = originalTitle; let title = $state(originalTitle);
let originalShowGitHub = data.ticket?.show_contributions ?? true; let originalShowGitHub = data.ticket?.show_contributions ?? true;
let showGitHub = originalShowGitHub; let showGitHub = $state(originalShowGitHub);
let customizing = false; let customizing = $state(false);
let saving = false; let saving = $state(false);
$: modified = !dequal( let modified = $derived(
{ !dequal(
name: originalName, {
title: originalTitle, name: originalName,
showGitHub: originalShowGitHub title: originalTitle,
}, showGitHub: originalShowGitHub
{ name, title, showGitHub } },
{ name, title, showGitHub }
)
); );
async function saveTicket() { async function saveTicket() {

View File

@@ -15,7 +15,7 @@
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import type { Integration } from './+page'; import type { Integration } from './+page';
export let data; let { data } = $props();
const title = 'Integrations' + TITLE_SUFFIX; const title = 'Integrations' + TITLE_SUFFIX;
const description = const description =
@@ -29,26 +29,22 @@
distance: 500 distance: 500
}; };
let result: ResultType<Integration> = []; let result: ResultType<Integration> = $state([]);
let hasQuery: boolean; let query = $state(decodeURIComponent($page.url.searchParams.get('search') ?? ''));
let query = writable(decodeURIComponent($page.url.searchParams.get('search') ?? '')); let hasQuery = $derived(query.length > 0);
$: query.subscribe((value) => {
hasQuery = value.length > 0;
});
// platform filters // platform filters
const platforms = ['All', ...data.platforms]; const platforms = ['All', ...data.platforms];
let activePlatform = 'All'; let activePlatform = $state('All');
// categories // categories
let activeCategory: string | null = null; let activeCategory: string | null = $state(null);
const handleQuery = (e: Event) => { const handleQuery = (e: Event) => {
const value = (e.currentTarget as HTMLInputElement).value; const value = (e.currentTarget as HTMLInputElement).value;
query.set(value); query = value;
}; };
onMount(() => { onMount(() => {
@@ -78,7 +74,7 @@
</svelte:head> </svelte:head>
<!-- binding for fuse --> <!-- binding for fuse -->
<Fuse list={data.list} options={fuseOptions} bind:query={$query} bind:result /> <Fuse list={data.list} options={fuseOptions} bind:query bind:result />
<Main> <Main>
<header class="web-u-sep-block-end web-u-padding-block-end-0 relative overflow-hidden pb-0"> <header class="web-u-sep-block-end web-u-padding-block-end-0 relative overflow-hidden pb-0">
<div class="hero web-u-padding-block-end-0 relative container"> <div class="hero web-u-padding-block-end-0 relative container">
@@ -136,11 +132,13 @@
label="Search" label="Search"
name="search" name="search"
placeholder="Search" placeholder="Search"
bind:value={$query} bind:value={query}
autocomplete="off" autocomplete="off"
on:input={handleQuery} oninput={handleQuery}
> >
<span class="web-icon-search" aria-hidden="true" slot="icon"></span> {#snippet icon()}
<span class="web-icon-search" aria-hidden="true"></span>
{/snippet}
</Input> </Input>
</section> </section>
<section class="flex flex-col"> <section class="flex flex-col">
@@ -162,7 +160,7 @@
} }
)} )}
class:active-tag={activePlatform === platform} class:active-tag={activePlatform === platform}
on:click={() => (activePlatform = platform)} onclick={() => (activePlatform = platform)}
>{platform}</button >{platform}</button
> >
</li> </li>
@@ -181,7 +179,7 @@
<select <select
class="web-input-text w-full appearance-none" class="web-input-text w-full appearance-none"
disabled={hasQuery} disabled={hasQuery}
on:change={(e) => onchange={(e) =>
goto(`#${e.currentTarget.value.toLowerCase()}`)} goto(`#${e.currentTarget.value.toLowerCase()}`)}
> >
{#each data.categories as category} {#each data.categories as category}
@@ -213,8 +211,7 @@
href={`#${category.slug}`} href={`#${category.slug}`}
class="web-link" class="web-link"
class:is-pink={category.slug === activeCategory} class:is-pink={category.slug === activeCategory}
on:click={() => onclick={() => activeCategory === category.slug}
activeCategory === category.slug}
>{category.heading}</a >{category.heading}</a
> >
</li> </li>
@@ -233,7 +230,7 @@
<h2 class="text-label text-primary">Search results</h2> <h2 class="text-label text-primary">Search results</h2>
<p class="text-description"> <p class="text-description">
{result.length > 0 ? result.length : 'No'} results found {result.length > 0 ? result.length : 'No'} results found
for "{$query}" for "{query}"
</p> </p>
</header> </header>
<div class="l-max-size-list-cards flex flex-col gap-8"> <div class="l-max-size-list-cards flex flex-col gap-8">
@@ -531,7 +528,7 @@
scroll-margin-top: f.pxToRem(120); scroll-margin-top: f.pxToRem(120);
} }
.l-max-size-list-cards { .l-max-size-list-cards {
&:where(:has(> ul > li:nth-child(10))) { &:where(:global(:has(> ul > li:nth-child(10)))) {
position: relative; position: relative;
&::before { &::before {
@@ -560,7 +557,7 @@
} }
} }
:where(:target) { :where(:global(:target)) {
.l-max-size-list-cards { .l-max-size-list-cards {
overflow: visible; overflow: visible;
max-block-size: none; max-block-size: none;

View File

@@ -636,9 +636,9 @@
class="icon-info" class="icon-info"
aria-hidden="true" aria-hidden="true"
></button> ></button>
<svelte:fragment slot="tooltip"> {#snippet tooltip()}
{row.info} {row.info}
</svelte:fragment> {/snippet}
</Tooltip> </Tooltip>
{/if} {/if}
</div> </div>

View File

@@ -26,8 +26,10 @@
Copy: 'Copy', Copy: 'Copy',
Copied: 'Copied!' Copied: 'Copied!'
} as const; } as const;
type CopyStatusType = keyof typeof CopyStatus;
type CopyStatusValue = (typeof CopyStatus)[CopyStatusType];
let copyText = CopyStatus.Copy; let copyText: CopyStatusValue = CopyStatus.Copy;
async function handleCopy() { async function handleCopy() {
await copy(content); await copy(content);
@@ -77,9 +79,9 @@
aria-label="copy code from code-snippet" aria-label="copy code from code-snippet"
><span class="web-icon-copy" aria-hidden="true"></span></button ><span class="web-icon-copy" aria-hidden="true"></span></button
> >
<svelte:fragment slot="tooltip"> {#snippet tooltip()}
{copyText} {copyText}
</svelte:fragment> {/snippet}
</Tooltip> </Tooltip>
</li> </li>
</ul> </ul>

View File

@@ -170,7 +170,9 @@ async function getLoggedInUser(request) {
/> />
</a> </a>
</li> </li>
<svelte:fragment slot="tooltip">{platform.name}</svelte:fragment> {#snippet tooltip()}
{platform.name}
{/snippet}
</Tooltip> </Tooltip>
{/each} {/each}
</ul> </ul>

View File

@@ -26,8 +26,10 @@
Copy: 'Copy', Copy: 'Copy',
Copied: 'Copied!' Copied: 'Copied!'
} as const; } as const;
type CopyStatusType = keyof typeof CopyStatus;
type CopyStatusValue = (typeof CopyStatus)[CopyStatusType];
let copyText = CopyStatus.Copy; let copyText: CopyStatusValue = CopyStatus.Copy;
async function handleCopy() { async function handleCopy() {
await copy(content); await copy(content);
@@ -77,9 +79,11 @@
aria-label="copy code from code-snippet" aria-label="copy code from code-snippet"
><span class="web-icon-copy" aria-hidden="true"></span></button ><span class="web-icon-copy" aria-hidden="true"></span></button
> >
<svelte:fragment slot="tooltip"> {#snippet tooltip()}
{copyText} <span>
</svelte:fragment> {copyText}
</span>
{/snippet}
</Tooltip> </Tooltip>
</li> </li>
</ul> </ul>

View File

@@ -108,9 +108,9 @@
+{hiddenRuntimes.length} +{hiddenRuntimes.length}
</span> </span>
</li> </li>
<svelte:fragment slot="tooltip"> {#snippet tooltip()}
<span class="text-micro">{hiddenRuntimes.join(', ')}</span> <span class="text-micro">{hiddenRuntimes.join(', ')}</span>
</svelte:fragment> {/snippet}
</Tooltip> </Tooltip>
{/if} {/if}
</ul> </ul>

View File

@@ -26,7 +26,10 @@
Copy: 'Copy', Copy: 'Copy',
Copied: 'Copied!' Copied: 'Copied!'
} as const; } as const;
let copyText = CopyStatus.Copy; type CopyStatusType = keyof typeof CopyStatus;
type CopyStatusValue = (typeof CopyStatus)[CopyStatusType];
let copyText: CopyStatusValue = CopyStatus.Copy;
async function handleCopy() { async function handleCopy() {
await copy(content); await copy(content);
@@ -75,9 +78,11 @@
aria-label="copy code from code-snippet" aria-label="copy code from code-snippet"
><span class="web-icon-copy" aria-hidden="true"></span></button ><span class="web-icon-copy" aria-hidden="true"></span></button
> >
<svelte:fragment slot="tooltip"> {#snippet tooltip()}
{copyText} <span>
</svelte:fragment> {copyText}
</span>
{/snippet}
</Tooltip> </Tooltip>
</li> </li>
</ul> </ul>

View File

@@ -18,12 +18,12 @@
"Appwrite's Threads page showcases our community interactions on Discord. Join the conversation, ask questions, or assist other members with their issues."; "Appwrite's Threads page showcases our community interactions on Discord. Join the conversation, ask questions, or assist other members with their issues.";
const ogImage = DEFAULT_HOST + '/images/open-graph/website.png'; const ogImage = DEFAULT_HOST + '/images/open-graph/website.png';
export let data; let { data } = $props();
let threads = data.threads; let threads = $state(data.threads);
let searching = false; // Do some sick animation let searching = false; // Do some sick animation
let query = ''; let query = $state('');
const handleSearch = async (value: string) => { const handleSearch = async (value: string) => {
query = value; query = value;
@@ -84,7 +84,7 @@
'REST API' 'REST API'
]; ];
let selectedTags: string[] = []; let selectedTags: string[] = $state([]);
function toggleTag(tag: string) { function toggleTag(tag: string) {
if (selectedTags.includes(tag)) { if (selectedTags.includes(tag)) {
@@ -149,7 +149,7 @@
<button <button
class="web-btn-tag" class="web-btn-tag"
class:is-selected={selectedTags?.includes(tag)} class:is-selected={selectedTags?.includes(tag)}
on:click={() => toggleTag(tag)} onclick={() => toggleTag(tag)}
> >
{tag} {tag}
</button> </button>
@@ -200,7 +200,7 @@
<span class="text-body font-medium">No support threads found</span> <span class="text-body font-medium">No support threads found</span>
<button <button
class="web-button" class="web-button"
on:click={() => { onclick={() => {
query = ''; query = '';
handleSearch(''); handleSearch('');
}}>Clear search</button }}>Clear search</button

View File

@@ -9,7 +9,7 @@
import PreFooter from '../PreFooter.svelte'; import PreFooter from '../PreFooter.svelte';
import MessageCard from './MessageCard.svelte'; import MessageCard from './MessageCard.svelte';
export let data; let { data } = $props();
const title = data.title + ' - Threads' + TITLE_SUFFIX; const title = data.title + ' - Threads' + TITLE_SUFFIX;
const description = DEFAULT_DESCRIPTION; const description = DEFAULT_DESCRIPTION;

View File

@@ -22,7 +22,11 @@
Copied: 'Copied!' Copied: 'Copied!'
} as const; } as const;
let copyText = CopyStatus.Copy; type CopyStatusType = keyof typeof CopyStatus;
type CopyStatusValue = (typeof CopyStatus)[CopyStatusType];
let copyText: CopyStatusValue = CopyStatus.Copy;
async function handleCopy() { async function handleCopy() {
await copy(text); await copy(text);
@@ -78,19 +82,18 @@
<ul class="buttons-list flex gap-2"> <ul class="buttons-list flex gap-2">
<li class="buttons-list-item ps-5"> <li class="buttons-list-item ps-5">
<Tooltip> <Tooltip>
<button {#snippet asChild(trigger)}
slot="asChild" <button
let:trigger on:click={handleCopy}
use:melt={trigger} class="web-icon-button"
on:click={handleCopy} aria-label="copy code from code-snippet"
class="web-icon-button" >
aria-label="copy code from code-snippet" <span class="web-icon-copy" aria-hidden="true"></span>
> </button>
<span class="web-icon-copy" aria-hidden="true"></span> {/snippet}
</button> {#snippet tooltip()}
<svelte:fragment slot="tooltip"> <span>{copyText}</span>
{copyText} {/snippet}
</svelte:fragment>
</Tooltip> </Tooltip>
</li> </li>
</ul> </ul>

View File

@@ -1,4 +1,3 @@
import { sentrySvelteKit } from '@sentry/sveltekit';
import dynamicImport from 'vite-plugin-dynamic-import'; import dynamicImport from 'vite-plugin-dynamic-import';
import { sveltekit } from '@sveltejs/kit/vite'; import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config'; import { defineConfig } from 'vitest/config';
@@ -8,13 +7,6 @@ import manifestSRI from 'vite-plugin-manifest-sri';
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
sentrySvelteKit({
adapter: 'node',
sourceMapsUploadOptions: {
org: 'appwrite',
project: 'website'
}
}),
enhancedImages(), enhancedImages(),
sveltekit(), sveltekit(),
dynamicImport({ dynamicImport({