mirror of
https://github.com/LukeHagar/website.git
synced 2025-12-09 21:07:46 +00:00
Merge branch 'main' into skip-e2e
This commit is contained in:
1
global.d.ts
vendored
Normal file
1
global.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
declare module 'reodotdev';
|
||||||
@@ -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
2121
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -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();
|
|
||||||
@@ -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();
|
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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} />
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 -->
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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. |
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}, {})
|
}, {})
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
Reference in New Issue
Block a user