mirror of
https://github.com/LukeHagar/website.git
synced 2025-12-06 04:22:07 +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/svelte": "^0.86.5",
|
||||
"@playwright/test": "^1.50.0",
|
||||
"@sentry/sveltekit": "^9.10.1",
|
||||
"@sveltejs/adapter-node": "^4.0.1",
|
||||
"@sveltejs/enhanced-img": "^0.3.9",
|
||||
"@sveltejs/adapter-node": "^5.2.12",
|
||||
"@sveltejs/enhanced-img": "^0.4.4",
|
||||
"@sveltejs/kit": "^2.20.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@tailwindcss/postcss": "^4.0.17",
|
||||
@@ -81,6 +80,7 @@
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"remeda": "^2.20.0",
|
||||
"reodotdev": "^1.0.0",
|
||||
"sass": "^1.83.4",
|
||||
"svelte": "^5.25.6",
|
||||
"svelte-check": "^4.0.0",
|
||||
@@ -96,12 +96,11 @@
|
||||
"vite-plugin-dynamic-import": "^1.6.0",
|
||||
"vite-plugin-image-optimizer": "^1.1.8",
|
||||
"vite-plugin-manifest-sri": "^0.2.0",
|
||||
"vitest": "^1.6.0"
|
||||
"vitest": "^3.1.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
"@sentry/cli",
|
||||
"core-js",
|
||||
"esbuild",
|
||||
"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">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
let carousel: HTMLElement;
|
||||
|
||||
export let size: 'default' | 'medium' | 'big' = 'default';
|
||||
export let gap = 32;
|
||||
interface Props {
|
||||
size?: 'default' | 'medium' | 'big';
|
||||
gap?: number;
|
||||
header: Snippet;
|
||||
children: Snippet;
|
||||
}
|
||||
|
||||
let { size = 'default', gap = 32, header, children }: Props = $props();
|
||||
let scroll = 0;
|
||||
|
||||
function calculateScrollAmount(prev = false) {
|
||||
@@ -32,8 +40,8 @@
|
||||
});
|
||||
}
|
||||
|
||||
let isEnd = false;
|
||||
let isStart = true;
|
||||
let isEnd = $state(false);
|
||||
let isStart = $state(true);
|
||||
|
||||
function handleScroll() {
|
||||
isStart = carousel.scrollLeft <= 0;
|
||||
@@ -43,13 +51,13 @@
|
||||
|
||||
<div>
|
||||
<div class="mt-2 flex flex-wrap items-center">
|
||||
<slot name="header" />
|
||||
{@render header()}
|
||||
<div class="nav ml-auto flex items-end gap-3">
|
||||
<button
|
||||
class="web-icon-button"
|
||||
aria-label="Move carousel backward"
|
||||
disabled={isStart}
|
||||
on:click={prev}
|
||||
onclick={prev}
|
||||
>
|
||||
<span class="web-icon-arrow-left" aria-hidden="true"></span>
|
||||
</button>
|
||||
@@ -57,7 +65,7 @@
|
||||
class="web-icon-button"
|
||||
aria-label="Move carousel forward"
|
||||
disabled={isEnd}
|
||||
on:click={next}
|
||||
onclick={next}
|
||||
>
|
||||
<span class="web-icon-arrow-right" aria-hidden="true"></span>
|
||||
</button>
|
||||
@@ -71,9 +79,9 @@
|
||||
class:is-big={size === 'big'}
|
||||
style:gap="{gap}px"
|
||||
bind:this={carousel}
|
||||
on:scroll={handleScroll}
|
||||
onscroll={handleScroll}
|
||||
>
|
||||
<slot />
|
||||
{@render children()}
|
||||
</ul>
|
||||
</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">
|
||||
import { Select, Tooltip } from '$lib/components';
|
||||
import { getCodeHtml, type Language } from '$lib/utils/code';
|
||||
@@ -26,8 +30,11 @@
|
||||
Copy: 'Copy',
|
||||
Copied: 'Copied!'
|
||||
} as const;
|
||||
type CopyStatusType = keyof typeof CopyStatus;
|
||||
type CopyStatusValue = (typeof CopyStatus)[CopyStatusType];
|
||||
|
||||
let copyText: CopyStatusValue = CopyStatus.Copy;
|
||||
|
||||
let copyText = CopyStatus.Copy;
|
||||
async function handleCopy() {
|
||||
await copy(content);
|
||||
|
||||
@@ -78,9 +85,11 @@
|
||||
aria-label="copy code from code-snippet"
|
||||
><span class="web-icon-copy" aria-hidden="true"></span></button
|
||||
>
|
||||
<svelte:fragment slot="tooltip">
|
||||
{copyText}
|
||||
</svelte:fragment>
|
||||
{#snippet tooltip()}
|
||||
<span>
|
||||
{copyText}
|
||||
</span>
|
||||
{/snippet}
|
||||
</Tooltip>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -85,7 +85,9 @@
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<svelte:fragment slot="tooltip">{platform.name}</svelte:fragment>
|
||||
{#snippet tooltip()}
|
||||
{platform.name}
|
||||
{/snippet}
|
||||
</Tooltip>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
@@ -72,15 +72,15 @@
|
||||
);
|
||||
</script>
|
||||
|
||||
{@render asChild?.({ trigger: $trigger })}
|
||||
|
||||
{#if !asChild && children}
|
||||
{#if asChild}
|
||||
{@render asChild({ trigger: $trigger })}
|
||||
{:else if children}
|
||||
<span use:melt={$trigger}>
|
||||
{@render children()}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
{#if $open && !disabled}
|
||||
{#if tooltip && $open && !disabled}
|
||||
<div use:melt={$content} class="web-tooltip text-sub-body" transition:fly={flyParams}>
|
||||
<div use:melt={$arrow}></div>
|
||||
{@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">
|
||||
import { classNames } from '$lib/utils/classnames';
|
||||
import type { Snippet } from 'svelte';
|
||||
import type { HTMLInputAttributes } from 'svelte/elements';
|
||||
|
||||
type $$Props = HTMLInputAttributes & {
|
||||
interface Props extends HTMLInputAttributes {
|
||||
label?: string;
|
||||
};
|
||||
icon?: Snippet;
|
||||
}
|
||||
|
||||
export let label: $$Props['label'] = '';
|
||||
export let type: $$Props['type'] = 'text';
|
||||
export let value: $$Props['value'] = '';
|
||||
const { class: classes, name, ...props } = $$restProps;
|
||||
let {
|
||||
label = '',
|
||||
type = 'text',
|
||||
value = $bindable(''),
|
||||
icon,
|
||||
class: classes,
|
||||
name,
|
||||
...rest
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if $$slots.icon}
|
||||
{#if icon}
|
||||
<label
|
||||
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',
|
||||
classes
|
||||
)}
|
||||
>
|
||||
<slot name="icon" />
|
||||
{@render icon?.()}
|
||||
{#key type}
|
||||
<input
|
||||
{name}
|
||||
{...{ type }}
|
||||
bind:value
|
||||
on:input
|
||||
on:change
|
||||
on:focus
|
||||
on:blur
|
||||
class="w-full border-0 ring-0 outline-none"
|
||||
{...props}
|
||||
{...rest}
|
||||
/>
|
||||
{/key}
|
||||
</label>
|
||||
@@ -45,15 +50,11 @@
|
||||
{name}
|
||||
{...{ type }}
|
||||
bind:value
|
||||
on:input
|
||||
on:change
|
||||
on:focus
|
||||
on:blur
|
||||
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',
|
||||
classes
|
||||
)}
|
||||
{...props}
|
||||
{...rest}
|
||||
/>
|
||||
{/key}
|
||||
{/if}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
export const GITHUB_STARS = '47K';
|
||||
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 SENTRY_DSN =
|
||||
'https://27d41dc8bb67b596f137924ab8599e59@o1063647.ingest.us.sentry.io/4507497727000576';
|
||||
|
||||
export const BLOG_POSTS_PER_PAGE = 12;
|
||||
|
||||
|
||||
@@ -72,7 +72,9 @@
|
||||
{#if expandable && !$layoutState.showSidenav}
|
||||
<Tooltip placement="right">
|
||||
<SidebarNavButton groupItem={navGroup} />
|
||||
<svelte:fragment slot="tooltip">{navGroup.label}</svelte:fragment>
|
||||
{#snippet tooltip()}
|
||||
<span>{navGroup.label}</span>
|
||||
{/snippet}
|
||||
</Tooltip>
|
||||
{:else}
|
||||
<SidebarNavButton groupItem={navGroup} />
|
||||
@@ -89,9 +91,9 @@
|
||||
{#if expandable && !$layoutState.showSidenav}
|
||||
<Tooltip placement="right">
|
||||
<SidebarNavButton {groupItem} />
|
||||
<svelte:fragment slot="tooltip"
|
||||
>{groupItem.label}</svelte:fragment
|
||||
>
|
||||
{#snippet tooltip()}
|
||||
<span>{groupItem.label}</span>
|
||||
{/snippet}
|
||||
</Tooltip>
|
||||
{:else}
|
||||
<SidebarNavButton {groupItem} />
|
||||
|
||||
@@ -35,8 +35,11 @@
|
||||
Copy: 'Copy',
|
||||
Copied: 'Copied!'
|
||||
} 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() {
|
||||
await copy(toCopy ?? content);
|
||||
|
||||
|
||||
@@ -52,8 +52,10 @@
|
||||
Copy: 'Copy',
|
||||
Copied: 'Copied!'
|
||||
} 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() {
|
||||
await copy($content);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts" context="module">
|
||||
import { derived, writable } from 'svelte/store';
|
||||
import { loadReoScript } from 'reodotdev';
|
||||
|
||||
export type Theme = 'dark' | 'light' | 'system';
|
||||
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>
|
||||
|
||||
<svelte:window on:scroll={handleScroll} />
|
||||
@@ -135,6 +145,7 @@
|
||||
{#if !dev}
|
||||
<!--suppress JSUnresolvedLibraryURL -->
|
||||
<script defer data-domain="appwrite.io" src="https://plausible.io/js/script.js"></script>
|
||||
|
||||
<!-- ZoomInfo snippet -->
|
||||
<script>
|
||||
window[
|
||||
@@ -178,22 +189,6 @@
|
||||
document.body.appendChild(zi);
|
||||
});
|
||||
</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}
|
||||
|
||||
<!-- canonical url -->
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
import { setContext } from 'svelte';
|
||||
import type { AuthorData, CategoryData, PostsData } from './content.js';
|
||||
|
||||
export let data;
|
||||
let { data, children } = $props();
|
||||
setContext<PostsData[]>('posts', data.posts);
|
||||
setContext<AuthorData[]>('authors', data.authors);
|
||||
setContext<CategoryData[]>('categories', data.categories);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
{@render children()}
|
||||
|
||||
@@ -8,20 +8,20 @@
|
||||
import { beforeNavigate, goto } from '$app/navigation';
|
||||
import { createDebounce } from '$lib/utils/debounce';
|
||||
|
||||
export let data;
|
||||
let { data } = $props();
|
||||
|
||||
const featured = data.featured;
|
||||
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 isEnd = false;
|
||||
let isStart = true;
|
||||
let query = $state('');
|
||||
let isEnd = $state(false);
|
||||
let isStart = $state(true);
|
||||
let categoriesElement: 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 searchQuery = query.toLowerCase();
|
||||
@@ -70,7 +70,7 @@
|
||||
});
|
||||
};
|
||||
|
||||
$: navigationLink = (pageNumber: number): string => {
|
||||
let navigationLink = $derived((pageNumber: number): string => {
|
||||
const currentUrl = $page.url;
|
||||
const url = new URL(`/blog/${pageNumber}`, currentUrl);
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
}
|
||||
|
||||
return url.toString();
|
||||
};
|
||||
});
|
||||
|
||||
const { debounce, reset } = createDebounce();
|
||||
|
||||
@@ -271,14 +271,14 @@
|
||||
>
|
||||
<ul
|
||||
class="categories flex gap-2 overflow-x-auto"
|
||||
on:scroll={handleScroll}
|
||||
onscroll={handleScroll}
|
||||
bind:this={categoriesElement}
|
||||
>
|
||||
<li class="flex items-center">
|
||||
<button
|
||||
class="web-interactive-tag web-caption-400 cursor-pointer"
|
||||
class:is-selected={selectedCategory === 'Latest'}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
selectedCategory = 'Latest';
|
||||
handleSearch();
|
||||
}}
|
||||
@@ -292,7 +292,7 @@
|
||||
<button
|
||||
class="web-interactive-tag web-caption-400 cursor-pointer"
|
||||
class:is-selected={selectedCategory === category.name}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
selectedCategory = category.name;
|
||||
handleSearch();
|
||||
}}
|
||||
@@ -351,7 +351,7 @@
|
||||
|
||||
<button
|
||||
class="web-button is-secondary"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
query = '';
|
||||
selectedCategory = 'Latest';
|
||||
handleSearch();
|
||||
|
||||
@@ -30,7 +30,7 @@ export type PostsData = {
|
||||
slug: string;
|
||||
featured?: boolean;
|
||||
unlisted?: boolean;
|
||||
callToAction: {
|
||||
callToAction?: {
|
||||
heading?: string;
|
||||
label?: string;
|
||||
url?: string;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import { CHANGELOG_KEY } from '../utils';
|
||||
import { TITLE_SUFFIX } from '$routes/titles';
|
||||
|
||||
export let data;
|
||||
let { data } = $props();
|
||||
|
||||
const seo = {
|
||||
title: 'Changelog' + TITLE_SUFFIX,
|
||||
@@ -55,15 +55,14 @@
|
||||
<li>
|
||||
<div class="web-dot"></div>
|
||||
<ChangelogEntry {entry}>
|
||||
<svelte:component this={entry.component} />
|
||||
<entry.component />
|
||||
</ChangelogEntry>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
{#if data.nextPage}
|
||||
<button class="web-button is-secondary" on:click={loadMore}
|
||||
>Load more</button
|
||||
<button class="web-button is-secondary" onclick={loadMore}>Load more</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import { DEFAULT_DESCRIPTION, DEFAULT_HOST } from '$lib/utils/metadata';
|
||||
import { CHANGELOG_TITLE_SUFFIX } from '$routes/titles';
|
||||
|
||||
export let data;
|
||||
let { data } = $props();
|
||||
|
||||
const seo = {
|
||||
title: data.title,
|
||||
@@ -25,8 +25,11 @@
|
||||
Copy: 'Copy',
|
||||
Copied: 'Copied!'
|
||||
} 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() {
|
||||
const blogPostUrl = encodeURI(`https://appwrite.io${$page.url.pathname}`);
|
||||
|
||||
@@ -126,11 +129,11 @@
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<svelte:fragment slot="tooltip">
|
||||
{#snippet tooltip()}
|
||||
{sharingOption.type === 'copy'
|
||||
? copyText
|
||||
: `Share on ${sharingOption.label}`}
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</Tooltip>
|
||||
</li>
|
||||
{/each}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="ts" context="module">
|
||||
<script lang="ts" module>
|
||||
export const events: EventCardProps[] = [
|
||||
{
|
||||
href: 'https://discord.com/events/564160730845151244/1279026334496067669/1286356126924800000',
|
||||
@@ -43,6 +43,8 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { preventDefault } from 'svelte/legacy';
|
||||
|
||||
import { Carousel } from '$lib/components';
|
||||
import FloatingHeads from '$lib/components/FloatingHeads.svelte';
|
||||
import FooterNav from '$lib/components/FooterNav.svelte';
|
||||
@@ -59,7 +61,7 @@
|
||||
import type { ProjectCardProps } from './ProjectCard.svelte';
|
||||
import ProjectCard from './ProjectCard.svelte';
|
||||
|
||||
export let data;
|
||||
let { data } = $props();
|
||||
|
||||
const projects: ProjectCardProps[] = [
|
||||
{
|
||||
@@ -103,11 +105,11 @@
|
||||
{ metric: '800+', description: 'Contributors' }
|
||||
];
|
||||
|
||||
let name = '';
|
||||
let email = '';
|
||||
let submitted = false;
|
||||
let error: string | undefined;
|
||||
let submitting = false;
|
||||
let name = $state('');
|
||||
let email = $state('');
|
||||
let submitted = $state(false);
|
||||
let error: string | undefined = $state();
|
||||
let submitting = $state(false);
|
||||
|
||||
async function submit() {
|
||||
submitting = true;
|
||||
@@ -423,9 +425,9 @@
|
||||
<div class="web-big-padding-section-level-2">
|
||||
<section class="web-u-sep-block-start web-u-padding-block-start-64 container">
|
||||
<Carousel size="big">
|
||||
<svelte:fragment slot="header">
|
||||
{#snippet header()}
|
||||
<h4 class="text-label text-primary">Upcoming Events</h4>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
{#each events as event}
|
||||
<li>
|
||||
<EventCard
|
||||
@@ -630,7 +632,10 @@
|
||||
{:else}
|
||||
<form
|
||||
method="post"
|
||||
on:submit|preventDefault={submit}
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
submit();
|
||||
}}
|
||||
class="flex flex-col gap-4"
|
||||
>
|
||||
<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_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_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_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. |
|
||||
|
||||
@@ -27,13 +27,13 @@
|
||||
import Response from './(components)/Response.svelte';
|
||||
import RateLimits from './(components)/RateLimits.svelte';
|
||||
|
||||
export let data;
|
||||
let { data } = $props();
|
||||
|
||||
setContext<LayoutContext>('headings', writable({}));
|
||||
|
||||
const headings = getContext<LayoutContext>('headings');
|
||||
|
||||
let selected: string | undefined = undefined;
|
||||
let selected: string | undefined = $state(undefined);
|
||||
headings.subscribe((n) => {
|
||||
const noVisible = Object.values(n).every((n) => !n.visible);
|
||||
if (selected && noVisible) {
|
||||
@@ -119,21 +119,22 @@
|
||||
});
|
||||
|
||||
// cleaned service description without Markdown links.
|
||||
$: serviceDescription = (data.service?.description ?? '').replace(
|
||||
/\[([^\]]+)]\([^)]+\)/g,
|
||||
'$1'
|
||||
let serviceDescription = $derived(
|
||||
(data.service?.description ?? '').replace(/\[([^\]]+)]\([^)]+\)/g, '$1')
|
||||
);
|
||||
|
||||
// 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;
|
||||
$: platform = ($preferredPlatform ?? $page.params.platform) as Platform;
|
||||
$: platformType = platform.startsWith('client-') ? 'CLIENT' : 'SERVER';
|
||||
$: serviceName = serviceMap[data.service?.name];
|
||||
$: title = serviceName + API_REFERENCE_TITLE_SUFFIX;
|
||||
$: description = shortenedDescription;
|
||||
$: ogImage = DEFAULT_HOST + '/images/open-graph/docs.png';
|
||||
let platformBindingForSelect = $derived($page.params.platform as Platform);
|
||||
let platform = $derived(($preferredPlatform ?? $page.params.platform) as Platform);
|
||||
let platformType = $derived(platform.startsWith('client-') ? 'CLIENT' : 'SERVER');
|
||||
let serviceName = $derived(serviceMap[data.service?.name]);
|
||||
let title = $derived(serviceName + API_REFERENCE_TITLE_SUFFIX);
|
||||
let description = $derived(shortenedDescription);
|
||||
let ogImage = $derived(DEFAULT_HOST + '/images/open-graph/docs.png');
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -302,7 +303,7 @@
|
||||
use:clickOutside={() => ($layoutState.showReferences = false)}
|
||||
>
|
||||
{#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>
|
||||
</button>
|
||||
<div class="web-references-menu-content">
|
||||
@@ -310,7 +311,7 @@
|
||||
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>
|
||||
<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>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import Tabs from '$markdoc/tags/Tabs.svelte';
|
||||
import TabsItem from '$markdoc/tags/TabsItem.svelte';
|
||||
|
||||
export let data;
|
||||
let { data } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
'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';
|
||||
|
||||
export let data;
|
||||
let { data } = $props();
|
||||
|
||||
type MappedTutorial = (typeof data.tutorials)[number];
|
||||
|
||||
|
||||
@@ -55,13 +55,14 @@ export async function load() {
|
||||
|
||||
const tutorials = Object.entries(
|
||||
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 (!acc[item.category]) {
|
||||
acc[item.category] = [];
|
||||
if (!acc[cat]) {
|
||||
acc[cat] = [];
|
||||
}
|
||||
|
||||
// Push the current item into the appropriate category
|
||||
acc[item.category].push(item);
|
||||
acc[cat].push(item);
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import { globToTutorial } from '$lib/utils/tutorials.js';
|
||||
import { setContext } from 'svelte';
|
||||
|
||||
export let data;
|
||||
let { data, children } = $props();
|
||||
const tutorials = globToTutorial(data);
|
||||
setContext('tutorials', tutorials);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
{@render children()}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import { globToTutorial } from '$lib/utils/tutorials.js';
|
||||
import { setContext } from 'svelte';
|
||||
|
||||
export let data;
|
||||
let { data, children } = $props();
|
||||
const tutorials = globToTutorial(data);
|
||||
setContext('tutorials', tutorials);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
{@render children()}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import { globToTutorial } from '$lib/utils/tutorials.js';
|
||||
import { setContext } from 'svelte';
|
||||
|
||||
export let data;
|
||||
let { data, children } = $props();
|
||||
const tutorials = globToTutorial(data);
|
||||
setContext('tutorials', tutorials);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
{@render children()}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import { globToTutorial } from '$lib/utils/tutorials.js';
|
||||
import { setContext } from 'svelte';
|
||||
|
||||
export let data;
|
||||
let { data, children } = $props();
|
||||
const tutorials = globToTutorial(data);
|
||||
setContext('tutorials', tutorials);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
{@render children()}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import { globToTutorial } from '$lib/utils/tutorials.js';
|
||||
import { setContext } from 'svelte';
|
||||
|
||||
export let data;
|
||||
let { data, children } = $props();
|
||||
const tutorials = globToTutorial(data);
|
||||
setContext('tutorials', tutorials);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
{@render children()}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import { globToTutorial } from '$lib/utils/tutorials.js';
|
||||
import { setContext } from 'svelte';
|
||||
|
||||
export let data;
|
||||
let { data, children } = $props();
|
||||
const tutorials = globToTutorial(data);
|
||||
setContext('tutorials', tutorials);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
{@render children()}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import { globToTutorial } from '$lib/utils/tutorials.js';
|
||||
import { setContext } from 'svelte';
|
||||
|
||||
export let data;
|
||||
let { data, children } = $props();
|
||||
const tutorials = globToTutorial(data);
|
||||
setContext('tutorials', tutorials);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
{@render children()}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import { globToTutorial } from '$lib/utils/tutorials.js';
|
||||
import { setContext } from 'svelte';
|
||||
|
||||
export let data;
|
||||
let { data, children } = $props();
|
||||
const tutorials = globToTutorial(data);
|
||||
setContext('tutorials', tutorials);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
{@render children()}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import { globToTutorial } from '$lib/utils/tutorials.js';
|
||||
import { setContext } from 'svelte';
|
||||
|
||||
export let data;
|
||||
let { data, children } = $props();
|
||||
const tutorials = globToTutorial(data);
|
||||
setContext('tutorials', tutorials);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
{@render children()}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import { globToTutorial } from '$lib/utils/tutorials.js';
|
||||
import { setContext } from 'svelte';
|
||||
|
||||
export let data;
|
||||
let { data, children } = $props();
|
||||
const tutorials = globToTutorial(data);
|
||||
setContext('tutorials', tutorials);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
{@render children()}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import { globToTutorial } from '$lib/utils/tutorials.js';
|
||||
import { setContext } from 'svelte';
|
||||
|
||||
export let data;
|
||||
let { data, children } = $props();
|
||||
const tutorials = globToTutorial(data);
|
||||
setContext('tutorials', tutorials);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
{@render children()}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import { globToTutorial } from '$lib/utils/tutorials.js';
|
||||
import { setContext } from 'svelte';
|
||||
|
||||
export let data;
|
||||
let { data, children } = $props();
|
||||
const tutorials = globToTutorial(data);
|
||||
setContext('tutorials', tutorials);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
{@render children()}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import { globToTutorial } from '$lib/utils/tutorials.js';
|
||||
import { setContext } from 'svelte';
|
||||
|
||||
export let data;
|
||||
let { data, children } = $props();
|
||||
const tutorials = globToTutorial(data);
|
||||
setContext('tutorials', tutorials);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
{@render children()}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import { globToTutorial } from '$lib/utils/tutorials.js';
|
||||
import { setContext } from 'svelte';
|
||||
|
||||
export let data;
|
||||
let { data, children } = $props();
|
||||
const tutorials = globToTutorial(data);
|
||||
setContext('tutorials', tutorials);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
{@render children()}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import { globToTutorial } from '$lib/utils/tutorials.js';
|
||||
import { setContext } from 'svelte';
|
||||
|
||||
export let data;
|
||||
let { data, children } = $props();
|
||||
const tutorials = globToTutorial(data);
|
||||
setContext('tutorials', tutorials);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
{@render children()}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import TicketPreview from '$routes/init-0/(components)/TicketPreview.svelte';
|
||||
import Ticket from '../../(components)/Ticket.svelte';
|
||||
|
||||
export let data;
|
||||
let { data } = $props();
|
||||
|
||||
let firstName = data.ticket?.name?.split(/\s/)[0] ?? '';
|
||||
const ogImage = `${$page.url.origin}/init-0/tickets/${data.ticket.$id}/og`;
|
||||
@@ -56,7 +56,7 @@
|
||||
<a class="web-button" href="/init-0/tickets">
|
||||
<span class="text">Get my Init ticket</span>
|
||||
</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="text">Copy ticket URL</span>
|
||||
|
||||
@@ -11,23 +11,25 @@
|
||||
import Form from './form.svelte';
|
||||
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;
|
||||
let tribe: string | undefined = data.ticket?.tribe ?? undefined;
|
||||
let showGitHub = data.ticket?.show_contributions ?? true;
|
||||
let drawerOpen = false;
|
||||
let customizing = false;
|
||||
let variant: TicketVariant = data.ticket.variant ?? 'default';
|
||||
let tribe: string | undefined = $state(data.ticket?.tribe ?? undefined);
|
||||
let showGitHub = $state(data.ticket?.show_contributions ?? true);
|
||||
let drawerOpen = $state(false);
|
||||
let customizing = $state(false);
|
||||
let variant: TicketVariant = $state(data.ticket.variant ?? 'default');
|
||||
|
||||
$: modified = !dequal(
|
||||
{
|
||||
name: data.ticket?.name,
|
||||
tribe: data.ticket?.tribe,
|
||||
showGitHub: data.ticket?.show_contributions
|
||||
},
|
||||
{ name, tribe, showGitHub }
|
||||
let modified = $derived(
|
||||
!dequal(
|
||||
{
|
||||
name: data.ticket?.name,
|
||||
tribe: data.ticket?.tribe,
|
||||
showGitHub: data.ticket?.show_contributions
|
||||
},
|
||||
{ name, tribe, showGitHub }
|
||||
)
|
||||
);
|
||||
|
||||
async function saveTicket() {
|
||||
@@ -57,12 +59,14 @@
|
||||
|
||||
const ticketUrl = `${$page.url.origin}/init-0/tickets/${data.ticket.$id}`;
|
||||
const { copied, copy } = createCopy(ticketUrl);
|
||||
$: twitterText = encodeURIComponent(
|
||||
[
|
||||
`Join Init and celebrate everything new with @appwrite`,
|
||||
``,
|
||||
`Claim your ticket. ${ticketUrl}`
|
||||
].join('\n')
|
||||
let twitterText = $derived(
|
||||
encodeURIComponent(
|
||||
[
|
||||
`Join Init and celebrate everything new with @appwrite`,
|
||||
``,
|
||||
`Claim your ticket. ${ticketUrl}`
|
||||
].join('\n')
|
||||
)
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -78,7 +82,7 @@
|
||||
<div class="hero">
|
||||
{#if customizing}
|
||||
<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>Back</span>
|
||||
@@ -110,14 +114,14 @@
|
||||
|
||||
<div class="info">
|
||||
<button
|
||||
on:click={() => (customizing = true)}
|
||||
onclick={() => (customizing = true)}
|
||||
class="web-button is-full-width u-margin-block-start-32"
|
||||
>
|
||||
<span class="text">Customize ticket</span>
|
||||
</button>
|
||||
|
||||
<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>
|
||||
<span class="text">Copy ticket URL</span>
|
||||
</button>
|
||||
@@ -155,7 +159,7 @@
|
||||
|
||||
{#if customizing}
|
||||
<div class="drawer" data-state={drawerOpen ? 'open' : 'closed'}>
|
||||
<button on:click={() => (drawerOpen = !drawerOpen)}>
|
||||
<button onclick={() => (drawerOpen = !drawerOpen)}>
|
||||
<div class="inner">
|
||||
<span class="text-label text-primary">Ticket Editor</span>
|
||||
<span class="web-icon-chevron-down"></span>
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
>
|
||||
<img {src} {alt} />
|
||||
</button>
|
||||
<svelte:fragment slot="tooltip">{alt}</svelte:fragment>
|
||||
{#snippet tooltip()}
|
||||
{alt}
|
||||
{/snippet}
|
||||
</Tooltip>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -57,7 +57,9 @@
|
||||
|
||||
<Tooltip placement="bottom">
|
||||
<Switch bind:checked={showGitHub} />
|
||||
<svelte:fragment slot="tooltip">Show GitHub contributions</svelte:fragment>
|
||||
{#snippet tooltip()}
|
||||
Show GitHub contributions
|
||||
{/snippet}
|
||||
</Tooltip>
|
||||
</div>
|
||||
{:else}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { createCopy } from '$lib/utils/copy';
|
||||
import { TicketPreview, Ticket } from '$routes/init/(components)/ticket';
|
||||
|
||||
export let data;
|
||||
let { data } = $props();
|
||||
|
||||
let firstName = data.ticket?.name?.split(/\s/)[0] ?? '';
|
||||
const ogImage = `${$page.url.origin}/init/tickets/${data.ticket.$id}/og`;
|
||||
@@ -52,7 +52,7 @@
|
||||
<a class="web-button" href="/init/tickets">
|
||||
<span class="text">Get my ticket</span>
|
||||
</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="text">Copy ticket URL</span>
|
||||
|
||||
@@ -10,25 +10,27 @@
|
||||
TicketDetails
|
||||
} from '$routes/init/(components)/ticket/index.js';
|
||||
|
||||
export let data;
|
||||
let { data } = $props();
|
||||
|
||||
let originalName = data.ticket?.name ?? '';
|
||||
let name = originalName;
|
||||
let name = $state(originalName);
|
||||
let originalTitle = data.ticket?.title ?? '';
|
||||
let title = originalTitle;
|
||||
let title = $state(originalTitle);
|
||||
let originalShowGitHub = data.ticket?.show_contributions ?? true;
|
||||
let showGitHub = originalShowGitHub;
|
||||
let showGitHub = $state(originalShowGitHub);
|
||||
|
||||
let customizing = false;
|
||||
let saving = false;
|
||||
let customizing = $state(false);
|
||||
let saving = $state(false);
|
||||
|
||||
$: modified = !dequal(
|
||||
{
|
||||
name: originalName,
|
||||
title: originalTitle,
|
||||
showGitHub: originalShowGitHub
|
||||
},
|
||||
{ name, title, showGitHub }
|
||||
let modified = $derived(
|
||||
!dequal(
|
||||
{
|
||||
name: originalName,
|
||||
title: originalTitle,
|
||||
showGitHub: originalShowGitHub
|
||||
},
|
||||
{ name, title, showGitHub }
|
||||
)
|
||||
);
|
||||
|
||||
async function saveTicket() {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import type { Integration } from './+page';
|
||||
|
||||
export let data;
|
||||
let { data } = $props();
|
||||
|
||||
const title = 'Integrations' + TITLE_SUFFIX;
|
||||
const description =
|
||||
@@ -29,26 +29,22 @@
|
||||
distance: 500
|
||||
};
|
||||
|
||||
let result: ResultType<Integration> = [];
|
||||
let result: ResultType<Integration> = $state([]);
|
||||
|
||||
let hasQuery: boolean;
|
||||
let query = writable(decodeURIComponent($page.url.searchParams.get('search') ?? ''));
|
||||
|
||||
$: query.subscribe((value) => {
|
||||
hasQuery = value.length > 0;
|
||||
});
|
||||
let query = $state(decodeURIComponent($page.url.searchParams.get('search') ?? ''));
|
||||
let hasQuery = $derived(query.length > 0);
|
||||
|
||||
// platform filters
|
||||
const platforms = ['All', ...data.platforms];
|
||||
|
||||
let activePlatform = 'All';
|
||||
let activePlatform = $state('All');
|
||||
|
||||
// categories
|
||||
let activeCategory: string | null = null;
|
||||
let activeCategory: string | null = $state(null);
|
||||
|
||||
const handleQuery = (e: Event) => {
|
||||
const value = (e.currentTarget as HTMLInputElement).value;
|
||||
query.set(value);
|
||||
query = value;
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
@@ -78,7 +74,7 @@
|
||||
</svelte:head>
|
||||
|
||||
<!-- 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>
|
||||
<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">
|
||||
@@ -136,11 +132,13 @@
|
||||
label="Search"
|
||||
name="search"
|
||||
placeholder="Search"
|
||||
bind:value={$query}
|
||||
bind:value={query}
|
||||
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>
|
||||
</section>
|
||||
<section class="flex flex-col">
|
||||
@@ -162,7 +160,7 @@
|
||||
}
|
||||
)}
|
||||
class:active-tag={activePlatform === platform}
|
||||
on:click={() => (activePlatform = platform)}
|
||||
onclick={() => (activePlatform = platform)}
|
||||
>{platform}</button
|
||||
>
|
||||
</li>
|
||||
@@ -181,7 +179,7 @@
|
||||
<select
|
||||
class="web-input-text w-full appearance-none"
|
||||
disabled={hasQuery}
|
||||
on:change={(e) =>
|
||||
onchange={(e) =>
|
||||
goto(`#${e.currentTarget.value.toLowerCase()}`)}
|
||||
>
|
||||
{#each data.categories as category}
|
||||
@@ -213,8 +211,7 @@
|
||||
href={`#${category.slug}`}
|
||||
class="web-link"
|
||||
class:is-pink={category.slug === activeCategory}
|
||||
on:click={() =>
|
||||
activeCategory === category.slug}
|
||||
onclick={() => activeCategory === category.slug}
|
||||
>{category.heading}</a
|
||||
>
|
||||
</li>
|
||||
@@ -233,7 +230,7 @@
|
||||
<h2 class="text-label text-primary">Search results</h2>
|
||||
<p class="text-description">
|
||||
{result.length > 0 ? result.length : 'No'} results found
|
||||
for "{$query}"
|
||||
for "{query}"
|
||||
</p>
|
||||
</header>
|
||||
<div class="l-max-size-list-cards flex flex-col gap-8">
|
||||
@@ -531,7 +528,7 @@
|
||||
scroll-margin-top: f.pxToRem(120);
|
||||
}
|
||||
.l-max-size-list-cards {
|
||||
&:where(:has(> ul > li:nth-child(10))) {
|
||||
&:where(:global(:has(> ul > li:nth-child(10)))) {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
@@ -560,7 +557,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
:where(:target) {
|
||||
:where(:global(:target)) {
|
||||
.l-max-size-list-cards {
|
||||
overflow: visible;
|
||||
max-block-size: none;
|
||||
|
||||
@@ -636,9 +636,9 @@
|
||||
class="icon-info"
|
||||
aria-hidden="true"
|
||||
></button>
|
||||
<svelte:fragment slot="tooltip">
|
||||
{#snippet tooltip()}
|
||||
{row.info}
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -26,8 +26,10 @@
|
||||
Copy: 'Copy',
|
||||
Copied: 'Copied!'
|
||||
} as const;
|
||||
type CopyStatusType = keyof typeof CopyStatus;
|
||||
type CopyStatusValue = (typeof CopyStatus)[CopyStatusType];
|
||||
|
||||
let copyText = CopyStatus.Copy;
|
||||
let copyText: CopyStatusValue = CopyStatus.Copy;
|
||||
async function handleCopy() {
|
||||
await copy(content);
|
||||
|
||||
@@ -77,9 +79,9 @@
|
||||
aria-label="copy code from code-snippet"
|
||||
><span class="web-icon-copy" aria-hidden="true"></span></button
|
||||
>
|
||||
<svelte:fragment slot="tooltip">
|
||||
{#snippet tooltip()}
|
||||
{copyText}
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</Tooltip>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -170,7 +170,9 @@ async function getLoggedInUser(request) {
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<svelte:fragment slot="tooltip">{platform.name}</svelte:fragment>
|
||||
{#snippet tooltip()}
|
||||
{platform.name}
|
||||
{/snippet}
|
||||
</Tooltip>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
@@ -26,8 +26,10 @@
|
||||
Copy: 'Copy',
|
||||
Copied: 'Copied!'
|
||||
} as const;
|
||||
type CopyStatusType = keyof typeof CopyStatus;
|
||||
type CopyStatusValue = (typeof CopyStatus)[CopyStatusType];
|
||||
|
||||
let copyText = CopyStatus.Copy;
|
||||
let copyText: CopyStatusValue = CopyStatus.Copy;
|
||||
async function handleCopy() {
|
||||
await copy(content);
|
||||
|
||||
@@ -77,9 +79,11 @@
|
||||
aria-label="copy code from code-snippet"
|
||||
><span class="web-icon-copy" aria-hidden="true"></span></button
|
||||
>
|
||||
<svelte:fragment slot="tooltip">
|
||||
{copyText}
|
||||
</svelte:fragment>
|
||||
{#snippet tooltip()}
|
||||
<span>
|
||||
{copyText}
|
||||
</span>
|
||||
{/snippet}
|
||||
</Tooltip>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -108,9 +108,9 @@
|
||||
+{hiddenRuntimes.length}
|
||||
</span>
|
||||
</li>
|
||||
<svelte:fragment slot="tooltip">
|
||||
{#snippet tooltip()}
|
||||
<span class="text-micro">{hiddenRuntimes.join(', ')}</span>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</ul>
|
||||
|
||||
@@ -26,7 +26,10 @@
|
||||
Copy: 'Copy',
|
||||
Copied: 'Copied!'
|
||||
} as const;
|
||||
let copyText = CopyStatus.Copy;
|
||||
type CopyStatusType = keyof typeof CopyStatus;
|
||||
type CopyStatusValue = (typeof CopyStatus)[CopyStatusType];
|
||||
|
||||
let copyText: CopyStatusValue = CopyStatus.Copy;
|
||||
async function handleCopy() {
|
||||
await copy(content);
|
||||
|
||||
@@ -75,9 +78,11 @@
|
||||
aria-label="copy code from code-snippet"
|
||||
><span class="web-icon-copy" aria-hidden="true"></span></button
|
||||
>
|
||||
<svelte:fragment slot="tooltip">
|
||||
{copyText}
|
||||
</svelte:fragment>
|
||||
{#snippet tooltip()}
|
||||
<span>
|
||||
{copyText}
|
||||
</span>
|
||||
{/snippet}
|
||||
</Tooltip>
|
||||
</li>
|
||||
</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.";
|
||||
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 query = '';
|
||||
let query = $state('');
|
||||
|
||||
const handleSearch = async (value: string) => {
|
||||
query = value;
|
||||
@@ -84,7 +84,7 @@
|
||||
'REST API'
|
||||
];
|
||||
|
||||
let selectedTags: string[] = [];
|
||||
let selectedTags: string[] = $state([]);
|
||||
|
||||
function toggleTag(tag: string) {
|
||||
if (selectedTags.includes(tag)) {
|
||||
@@ -149,7 +149,7 @@
|
||||
<button
|
||||
class="web-btn-tag"
|
||||
class:is-selected={selectedTags?.includes(tag)}
|
||||
on:click={() => toggleTag(tag)}
|
||||
onclick={() => toggleTag(tag)}
|
||||
>
|
||||
{tag}
|
||||
</button>
|
||||
@@ -200,7 +200,7 @@
|
||||
<span class="text-body font-medium">No support threads found</span>
|
||||
<button
|
||||
class="web-button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
query = '';
|
||||
handleSearch('');
|
||||
}}>Clear search</button
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import PreFooter from '../PreFooter.svelte';
|
||||
import MessageCard from './MessageCard.svelte';
|
||||
|
||||
export let data;
|
||||
let { data } = $props();
|
||||
|
||||
const title = data.title + ' - Threads' + TITLE_SUFFIX;
|
||||
const description = DEFAULT_DESCRIPTION;
|
||||
|
||||
@@ -22,7 +22,11 @@
|
||||
Copied: 'Copied!'
|
||||
} as const;
|
||||
|
||||
let copyText = CopyStatus.Copy;
|
||||
type CopyStatusType = keyof typeof CopyStatus;
|
||||
type CopyStatusValue = (typeof CopyStatus)[CopyStatusType];
|
||||
|
||||
let copyText: CopyStatusValue = CopyStatus.Copy;
|
||||
|
||||
async function handleCopy() {
|
||||
await copy(text);
|
||||
|
||||
@@ -78,19 +82,18 @@
|
||||
<ul class="buttons-list flex gap-2">
|
||||
<li class="buttons-list-item ps-5">
|
||||
<Tooltip>
|
||||
<button
|
||||
slot="asChild"
|
||||
let:trigger
|
||||
use:melt={trigger}
|
||||
on:click={handleCopy}
|
||||
class="web-icon-button"
|
||||
aria-label="copy code from code-snippet"
|
||||
>
|
||||
<span class="web-icon-copy" aria-hidden="true"></span>
|
||||
</button>
|
||||
<svelte:fragment slot="tooltip">
|
||||
{copyText}
|
||||
</svelte:fragment>
|
||||
{#snippet asChild(trigger)}
|
||||
<button
|
||||
on:click={handleCopy}
|
||||
class="web-icon-button"
|
||||
aria-label="copy code from code-snippet"
|
||||
>
|
||||
<span class="web-icon-copy" aria-hidden="true"></span>
|
||||
</button>
|
||||
{/snippet}
|
||||
{#snippet tooltip()}
|
||||
<span>{copyText}</span>
|
||||
{/snippet}
|
||||
</Tooltip>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { sentrySvelteKit } from '@sentry/sveltekit';
|
||||
import dynamicImport from 'vite-plugin-dynamic-import';
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
@@ -8,13 +7,6 @@ import manifestSRI from 'vite-plugin-manifest-sri';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
sentrySvelteKit({
|
||||
adapter: 'node',
|
||||
sourceMapsUploadOptions: {
|
||||
org: 'appwrite',
|
||||
project: 'website'
|
||||
}
|
||||
}),
|
||||
enhancedImages(),
|
||||
sveltekit(),
|
||||
dynamicImport({
|
||||
|
||||
Reference in New Issue
Block a user