mirror of
https://github.com/LukeHagar/website.git
synced 2025-12-06 12:57:48 +00:00
Merge branch 'main' into button-component
This commit is contained in:
@@ -27,7 +27,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@number-flow/svelte": "^0.3.3",
|
"@number-flow/svelte": "^0.3.3",
|
||||||
"h3": "^1.14.0",
|
"h3": "^1.14.0",
|
||||||
"melt": "^0.28.0",
|
"melt": "^0.28.2",
|
||||||
"posthog-js": "^1.210.2",
|
"posthog-js": "^1.210.2",
|
||||||
"sharp": "^0.33.5"
|
"sharp": "^0.33.5"
|
||||||
},
|
},
|
||||||
|
|||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -15,8 +15,8 @@ importers:
|
|||||||
specifier: ^1.14.0
|
specifier: ^1.14.0
|
||||||
version: 1.15.1
|
version: 1.15.1
|
||||||
melt:
|
melt:
|
||||||
specifier: ^0.28.0
|
specifier: ^0.28.2
|
||||||
version: 0.28.0(@floating-ui/dom@1.6.13)(svelte@5.25.6)
|
version: 0.28.2(@floating-ui/dom@1.6.13)(svelte@5.25.6)
|
||||||
posthog-js:
|
posthog-js:
|
||||||
specifier: ^1.210.2
|
specifier: ^1.210.2
|
||||||
version: 1.230.4
|
version: 1.230.4
|
||||||
@@ -2726,8 +2726,8 @@ packages:
|
|||||||
meilisearch@0.37.0:
|
meilisearch@0.37.0:
|
||||||
resolution: {integrity: sha512-LdbK6JmRghCawrmWKJSEQF0OiE82md+YqJGE/U2JcCD8ROwlhTx0KM6NX4rQt0u0VpV0QZVG9umYiu3CSSIJAQ==}
|
resolution: {integrity: sha512-LdbK6JmRghCawrmWKJSEQF0OiE82md+YqJGE/U2JcCD8ROwlhTx0KM6NX4rQt0u0VpV0QZVG9umYiu3CSSIJAQ==}
|
||||||
|
|
||||||
melt@0.28.0:
|
melt@0.28.2:
|
||||||
resolution: {integrity: sha512-kiqaTgNB/IkADmUfJZKROqQ3z+isal8LjLhckQANqjfjggIosHM8M7RO3Og7IQ12zK06nLnwanL80SuTPhblrw==}
|
resolution: {integrity: sha512-55DGQ4B3bHKnDnK1ECJ46D+xythNKvuil60k0RXWJ3eEY5XsGjT6WZPVpRooYLfMlAki2J7JCWglCMYzvXwxVw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@floating-ui/dom': ^1.6.0
|
'@floating-ui/dom': ^1.6.0
|
||||||
svelte: ^5.0.0
|
svelte: ^5.0.0
|
||||||
@@ -6407,7 +6407,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- encoding
|
- encoding
|
||||||
|
|
||||||
melt@0.28.0(@floating-ui/dom@1.6.13)(svelte@5.25.6):
|
melt@0.28.2(@floating-ui/dom@1.6.13)(svelte@5.25.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@floating-ui/dom': 1.6.13
|
'@floating-ui/dom': 1.6.13
|
||||||
jest-axe: 9.0.0
|
jest-axe: 9.0.0
|
||||||
|
|||||||
33
src/app.css
33
src/app.css
@@ -164,39 +164,39 @@
|
|||||||
/* Font sizes */
|
/* Font sizes */
|
||||||
--text-x-micro: 0.625rem;
|
--text-x-micro: 0.625rem;
|
||||||
--text-x-micro--line-height: 0.875rem;
|
--text-x-micro--line-height: 0.875rem;
|
||||||
--text-x-micro--tracking: var(--tracking-tighter);
|
--text-x-micro--letter-spacing: var(--tracking-tighter);
|
||||||
--text-micro: 0.75rem;
|
--text-micro: 0.75rem;
|
||||||
--text-micro--line-height: 1rem;
|
--text-micro--line-height: 1rem;
|
||||||
--text-micro--tracking: var(--tracking-tighter);
|
--text-micro--letter-spacing: var(--tracking-tighter);
|
||||||
--text-caption: 0.875rem;
|
--text-caption: 0.875rem;
|
||||||
--text-caption--line-height: 1.375rem;
|
--text-caption--line-height: 1.375rem;
|
||||||
--text-caption--tracking: var(--tracking-tight);
|
--text-caption--letter-spacing: var(--tracking-tight);
|
||||||
--text-sub-body: clamp(0.875rem, 2vw, 1rem);
|
--text-sub-body: clamp(0.875rem, 2vw, 1rem);
|
||||||
--text-sub-body--line-height: 1.375rem;
|
--text-sub-body--line-height: 1.375rem;
|
||||||
--text-sub-body--tracking: var(--tracking-tight);
|
--text-sub-body--letter-spacing: var(--tracking-tight);
|
||||||
--text-body: clamp(1rem, 2.5vw, 1.125rem);
|
--text-body: clamp(1rem, 2.5vw, 1.125rem);
|
||||||
--text-body--line-height: clamp(1.375rem, 3vw, 1.625rem);
|
--text-body--line-height: clamp(1.375rem, 3vw, 1.625rem);
|
||||||
--text-body--tracking: var(--tracking-tight);
|
--text-body--letter-spacing: var(--tracking-tight);
|
||||||
--text-paragraph-md: 1rem;
|
--text-paragraph-md: 1rem;
|
||||||
--text-paragraph-md--line-height: 1.625rem;
|
--text-paragraph-md--line-height: 1.625rem;
|
||||||
--text-paragraph-md--tracking: var(--tracking-tight);
|
--text-paragraph-md--letter-spacing: var(--tracking-tight);
|
||||||
--text-paragraph-lg: 1.125rem;
|
--text-paragraph-lg: 1.125rem;
|
||||||
--text-paragraph-lg--line-height: 1.75rem;
|
--text-paragraph-lg--line-height: 1.75rem;
|
||||||
--text-paragraph-lg--tracking: var(--tracking-tight);
|
--text-paragraph-lg--letter-spacing: var(--tracking-tight);
|
||||||
--text-description: clamp(1.125rem, 3vw, 1.25rem);
|
--text-description: clamp(1.125rem, 3vw, 1.25rem);
|
||||||
--text-description--line-height: clamp(1.625rem, 3.5vw, 1.75rem);
|
--text-description--line-height: clamp(1.625rem, 3.5vw, 1.75rem);
|
||||||
--text-description--tracking: var(--tracking-tighter);
|
--text-description--letter-spacing: var(--tracking-tighter);
|
||||||
--text-label: 1.5rem;
|
--text-label: 1.5rem;
|
||||||
--text-label--line-height: 1.75rem;
|
--text-label--line-height: 1.75rem;
|
||||||
--text-title: clamp(2rem, 5vw, 2.5rem);
|
--text-title: clamp(2rem, 5vw, 2.5rem);
|
||||||
--text-title--line-height: clamp(2.125rem, 5.5vw, 2.75rem);
|
--text-title--line-height: clamp(2.125rem, 5.5vw, 2.75rem);
|
||||||
--text-title--tracking: var(--tracking-squeezed);
|
--text-title--letter-spacing: var(--tracking-squeezed);
|
||||||
--text-display: clamp(3rem, 7vw, 4rem);
|
--text-display: clamp(3rem, 7vw, 4rem);
|
||||||
--text-display--line-height: clamp(3.125rem, 7.5vw, 4.25rem);
|
--text-display--line-height: clamp(3.125rem, 7.5vw, 4.25rem);
|
||||||
--text-display--tracking: var(--tracking-compressed);
|
--text-display--letter-spacing: var(--tracking-compressed);
|
||||||
--text-headline: clamp(3.5rem, 8vw, 5.5rem);
|
--text-headline: clamp(3.5rem, 8vw, 5.5rem);
|
||||||
--text-headline--line-height: clamp(3.5rem, 8.5vw, 5.75rem);
|
--text-headline--line-height: clamp(3.5rem, 8.5vw, 5.75rem);
|
||||||
--text-headline--tracking: var(--tracking-compressed);
|
--text-headline--letter-spacing: var(--tracking-compressed);
|
||||||
|
|
||||||
/* Letter spacing */
|
/* Letter spacing */
|
||||||
--tracking-*: initial;
|
--tracking-*: initial;
|
||||||
@@ -208,11 +208,14 @@
|
|||||||
--tracking-loose: 0.08em;
|
--tracking-loose: 0.08em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer components {
|
@utility container {
|
||||||
.container {
|
margin-inline: auto;
|
||||||
@apply mx-auto box-content max-w-[75rem] px-5;
|
padding-inline: calc(var(--spacing) * 5);
|
||||||
}
|
box-sizing: box-content;
|
||||||
|
max-width: 75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer components {
|
||||||
.mask {
|
.mask {
|
||||||
mask-image: linear-gradient(
|
mask-image: linear-gradient(
|
||||||
to var(--mask-direction, top),
|
to var(--mask-direction, top),
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
interface Props {
|
interface Props {
|
||||||
size?: 'default' | 'medium' | 'big';
|
size?: 'default' | 'medium' | 'big';
|
||||||
gap?: number;
|
gap?: number;
|
||||||
header: Snippet;
|
header?: Snippet;
|
||||||
children: Snippet;
|
children: Snippet;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { size = 'default', gap = 32, header, children }: Props = $props();
|
const { size = 'default', gap = 32, header, children }: Props = $props();
|
||||||
let scroll = 0;
|
let scroll = 0;
|
||||||
|
|
||||||
function calculateScrollAmount(prev = false) {
|
function calculateScrollAmount(prev = false) {
|
||||||
@@ -51,7 +51,9 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="mt-2 flex flex-wrap items-center">
|
<div class="mt-2 flex flex-wrap items-center">
|
||||||
{@render header()}
|
{#if header}
|
||||||
|
{@render header()}
|
||||||
|
{/if}
|
||||||
<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"
|
||||||
|
|||||||
@@ -1,121 +1,115 @@
|
|||||||
<script lang="ts" module>
|
<script lang="ts" module>
|
||||||
import type { Writable } from 'svelte/store';
|
import { writable, type Writable } from 'svelte/store';
|
||||||
|
import { Tabs } from 'melt/builders';
|
||||||
|
|
||||||
|
export type TabsItemProps = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type TabsContext = Writable<{
|
export type TabsContext = Writable<{
|
||||||
content: ReturnType<typeof createTabs>['elements']['content'];
|
triggers: Array<TabsItemProps>;
|
||||||
triggers: Map<string, string>;
|
tabs: Tabs<string>;
|
||||||
}>;
|
}>;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Select from '$lib/components/Select.svelte';
|
|
||||||
import { classNames } from '$lib/utils/classnames';
|
import { classNames } from '$lib/utils/classnames';
|
||||||
import { createTabs } from '@melt-ui/svelte';
|
|
||||||
import { setContext, type Snippet } from 'svelte';
|
import { setContext, type Snippet } from 'svelte';
|
||||||
import { writable } from 'svelte/store';
|
import { Select } from '$lib/components';
|
||||||
|
|
||||||
interface Props {
|
const tabs = new Tabs<string>({
|
||||||
children: Snippet;
|
value: ''
|
||||||
}
|
});
|
||||||
|
|
||||||
const { children }: Props = $props();
|
|
||||||
|
|
||||||
const {
|
|
||||||
elements: { root, list, content, trigger },
|
|
||||||
states: { value }
|
|
||||||
} = createTabs();
|
|
||||||
|
|
||||||
const ctx = setContext<TabsContext>(
|
const ctx = setContext<TabsContext>(
|
||||||
'tabs',
|
'tabs',
|
||||||
writable({
|
writable({
|
||||||
content,
|
triggers: [],
|
||||||
triggers: new Map()
|
tabs
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
setContext('tabs-selection', value);
|
$effect(() => {
|
||||||
|
if ($ctx.triggers.length > 0 && !$ctx.tabs.value) {
|
||||||
|
$ctx.tabs.value = $ctx.triggers[0].id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
type TabsProps = {
|
||||||
|
children: Snippet;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { children }: TabsProps = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="web-card is-normal mt-4" {...$root} use:root>
|
<div
|
||||||
|
class="dark:bg-greyscale-850/90 mt-4 mb-8 flex flex-col gap-1 rounded-2xl border border-black/8 bg-white/90 px-6 pt-4 pb-6 outline-0 dark:border-white/10"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="tabs flex items-center gap-4 overflow-scroll"
|
class="flex items-center gap-4 overflow-scroll [-ms-overflow-style:none] [scrollbard-width:none]"
|
||||||
style="scrollbar-width: none; -ms-overflow-style: none;"
|
|
||||||
>
|
>
|
||||||
<ul class="tabs-list hidden items-center gap-4 sm:flex" {...$list} use:list>
|
<div class="hidden items-center gap-4 sm:flex" {...tabs.triggerList}>
|
||||||
{#each Array.from($ctx.triggers.entries()).slice(0, 7) as [id, title]}
|
{#each $ctx.triggers.slice(0, 7) as { title, id }}
|
||||||
<li
|
<button
|
||||||
class="tabs-item shrink-0 rounded-t-[0.625rem] text-center hover:bg-white/4"
|
class={classNames(
|
||||||
class:text-[var(--color-primary)]={$value === id}
|
'shrink-0 rounded-t-[0.625rem] text-center hover:bg-white/4',
|
||||||
|
'relative cursor-pointer bg-clip-padding px-1 py-[0.625rem] font-light outline-none',
|
||||||
|
'after:relative after:top-1 after:bottom-0 after:block after:h-px after:transition-all',
|
||||||
|
{
|
||||||
|
'after:bg-[var(--color-primary)]': tabs.value === id
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
{...tabs.getTrigger(id)}
|
||||||
>
|
>
|
||||||
<button
|
{title}
|
||||||
class={classNames(
|
</button>
|
||||||
'tabs-button relative cursor-pointer bg-clip-padding px-1 py-[0.625rem] font-light outline-none',
|
|
||||||
'after:relative after:top-1 after:bottom-0 after:block after:h-px after:transition-all',
|
|
||||||
{
|
|
||||||
'after:bg-[var(--color-primary)]': $value === id
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
{...$trigger(id)}
|
|
||||||
use:trigger>{title}</button
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
{/each}
|
{/each}
|
||||||
{#if Array.from($ctx.triggers.entries()).slice(7).length}
|
{#if $ctx.triggers.slice(7).length}
|
||||||
{@const entries = Array.from($ctx.triggers.entries())}
|
{@const desktopOptions = $ctx.triggers.slice(7)}
|
||||||
{@const desktopOptions = entries.slice(7)}
|
<Select
|
||||||
|
initialLabel="More"
|
||||||
<li>
|
options={desktopOptions.map(({ id, title }) => {
|
||||||
<Select
|
return {
|
||||||
initialLabel="More"
|
value: id,
|
||||||
options={desktopOptions.map(([value, label]) => {
|
label: title
|
||||||
return {
|
};
|
||||||
value,
|
})}
|
||||||
label
|
bind:value={$ctx.tabs.value}
|
||||||
};
|
/>
|
||||||
})}
|
|
||||||
bind:value={$value}
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
{/if}
|
{/if}
|
||||||
</ul>
|
</div>
|
||||||
<ul class="tabs-list flex items-center gap-4 sm:hidden" {...$list} use:list>
|
<div class="flex items-center gap-4 sm:hidden" {...tabs.triggerList}>
|
||||||
{#each Array.from($ctx.triggers.entries()).slice(0, 2) as [id, title]}
|
{#each $ctx.triggers.slice(0, 2) as { title, id }}
|
||||||
<li
|
<button
|
||||||
class="tabs-item shrink-0 rounded-t-[0.625rem] text-center hover:bg-white/4"
|
class={classNames(
|
||||||
class:text-[var(--color-primary)]={$value === id}
|
'shrink-0 rounded-t-[0.625rem] text-center hover:bg-white/4',
|
||||||
|
'relative cursor-pointer bg-clip-padding px-1 py-[0.625rem] font-light outline-none',
|
||||||
|
'after:relative after:top-1 after:bottom-0 after:block after:h-px after:transition-all',
|
||||||
|
{
|
||||||
|
'after:bg-[var(--color-primary)]': tabs.value === id
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
{...tabs.getTrigger(id)}
|
||||||
>
|
>
|
||||||
<button
|
{title}
|
||||||
class={classNames(
|
</button>
|
||||||
'tabs-button relative cursor-pointer bg-clip-padding px-1 py-[0.625rem] font-light outline-none',
|
|
||||||
'after:relative after:top-1 after:bottom-0 after:block after:h-px after:transition-all',
|
|
||||||
{
|
|
||||||
'after:bg-[var(--color-primary)]': $value === id
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
{...$trigger(id)}
|
|
||||||
use:trigger
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{/each}
|
{/each}
|
||||||
{#if Array.from($ctx.triggers.entries()).slice(2).length}
|
{#if $ctx.triggers.slice(2).length}
|
||||||
{@const entries = Array.from($ctx.triggers.entries())}
|
{@const desktopOptions = $ctx.triggers.slice(7)}
|
||||||
{@const desktopOptions = entries.slice(2)}
|
<Select
|
||||||
|
initialLabel="More"
|
||||||
<li>
|
options={desktopOptions.map(({ id, title }) => {
|
||||||
<Select
|
return {
|
||||||
initialLabel="More"
|
value: id,
|
||||||
options={desktopOptions.map(([value, label]) => {
|
label: title
|
||||||
return {
|
};
|
||||||
value,
|
})}
|
||||||
label
|
bind:value={$ctx.tabs.value}
|
||||||
};
|
/>
|
||||||
})}
|
|
||||||
bind:value={$value}
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
{/if}
|
{/if}
|
||||||
</ul>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{@render children?.()}
|
|
||||||
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,25 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, type Snippet } from 'svelte';
|
import { getContext, type Snippet } from 'svelte';
|
||||||
import type { TabsContext } from './Tabs.svelte';
|
import { type TabsContext, type TabsItemProps } from './Tabs.svelte';
|
||||||
|
|
||||||
interface Props {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
children: Snippet;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { id, title, children }: Props = $props();
|
|
||||||
|
|
||||||
const ctx = getContext<TabsContext>('tabs');
|
const ctx = getContext<TabsContext>('tabs');
|
||||||
|
|
||||||
const { content } = $ctx;
|
const { id, title, children }: TabsItemProps & { children: Snippet } = $props();
|
||||||
|
|
||||||
ctx.update((n) => {
|
$effect(() => {
|
||||||
n.triggers.set(id, title);
|
ctx.update((context) => {
|
||||||
return n;
|
context.triggers.push({ id, title });
|
||||||
|
return context;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="web-u-sep-block-start pt-4" {...$content(id)} use:content>
|
<div class="border-smooth border-t pt-4" {...$ctx.tabs.getContent(id)}>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ description: Learn how to manage team invites in Appwrite. Implement both client
|
|||||||
|
|
||||||
Appwrite provides two approaches for adding members to teams: client-side email invites and server-side custom flows. Each approach serves different use cases and offers unique benefits.
|
Appwrite provides two approaches for adding members to teams: client-side email invites and server-side custom flows. Each approach serves different use cases and offers unique benefits.
|
||||||
|
|
||||||
# Invite client-side {% #client-side %}
|
# Invite client-side
|
||||||
|
|
||||||
Client-side email invites are perfect for implementing user-to-user invitations, allowing your users to invite others to join their teams, organizations, or shared resources. When creating a membership, Appwrite:
|
Client-side email invites are perfect for implementing user-to-user invitations, allowing your users to invite others to join their teams, organizations, or shared resources. When creating a membership, Appwrite:
|
||||||
1. Creates a new user account if one doesn't exist for the email address
|
1. Creates a new user account if one doesn't exist for the email address
|
||||||
@@ -91,7 +91,7 @@ val response = teams.createMembership(
|
|||||||
```
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
## Accept invitations {% #accept-invitations %}
|
## Accept invitations
|
||||||
|
|
||||||
For client-side email invites, users must accept the invitation to join the team. The acceptance flow:
|
For client-side email invites, users must accept the invitation to join the team. The acceptance flow:
|
||||||
1. User receives an email with an invitation link containing a secret token
|
1. User receives an email with an invitation link containing a secret token
|
||||||
@@ -171,7 +171,7 @@ val response = teams.updateMembershipStatus(
|
|||||||
```
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
# Server-side custom flows {% #server-side %}
|
# Server-side custom flows
|
||||||
|
|
||||||
Server-side membership creation bypasses the email invitation process, allowing direct member addition. This approach:
|
Server-side membership creation bypasses the email invitation process, allowing direct member addition. This approach:
|
||||||
1. Creates an active membership immediately
|
1. Creates an active membership immediately
|
||||||
@@ -258,11 +258,11 @@ val response = teams.createMembership(
|
|||||||
```
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
# Manage memberships {% #manage-memberships %}
|
# Manage memberships
|
||||||
|
|
||||||
Once team memberships are created, you'll need to manage their lifecycle. This includes checking status, updating roles, and removing members when necessary.
|
Once team memberships are created, you'll need to manage their lifecycle. This includes checking status, updating roles, and removing members when necessary.
|
||||||
|
|
||||||
## Check membership status {% #status %}
|
## Check membership status
|
||||||
|
|
||||||
Before performing actions on team memberships, you often need to verify a user's current status within a team. The process differs between client-side and server-side implementations.
|
Before performing actions on team memberships, you often need to verify a user's current status within a team. The process differs between client-side and server-side implementations.
|
||||||
|
|
||||||
@@ -497,7 +497,7 @@ teamsList.teams.forEach { team ->
|
|||||||
```
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
## Remove members {% #remove-members %}
|
## Remove members
|
||||||
|
|
||||||
Team owners can remove members or users can leave teams:
|
Team owners can remove members or users can leave teams:
|
||||||
|
|
||||||
@@ -561,11 +561,11 @@ teams.deleteMembership(
|
|||||||
```
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
# Manage team permissions {% #permissions %}
|
# Manage team permissions
|
||||||
|
|
||||||
Teams in Appwrite use a role-based access control (RBAC) system. Each team member can be assigned one or more roles that define their permissions within the team.
|
Teams in Appwrite use a role-based access control (RBAC) system. Each team member can be assigned one or more roles that define their permissions within the team.
|
||||||
|
|
||||||
## Update roles {% #update-roles %}
|
## Update roles
|
||||||
|
|
||||||
You can assign roles when creating a membership or update them later. Note that only team members with the owner role can update other members' roles:
|
You can assign roles when creating a membership or update them later. Note that only team members with the owner role can update other members' roles:
|
||||||
|
|
||||||
@@ -637,7 +637,7 @@ teams.updateMembership(
|
|||||||
```
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
## Check role access {% #check-role-access %}
|
## Check role access
|
||||||
|
|
||||||
You can verify if a user has specific roles:
|
You can verify if a user has specific roles:
|
||||||
|
|
||||||
|
|||||||
@@ -135,6 +135,11 @@ query {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
GET /v1/databases/<DATABASE_ID>/collections/<COLLECTION_ID>/documents?queries[]=%7B%22method%22%3A%22equal%22%2C%22attribute%22%3A%22title%22%2C%22values%22%3A%5B%22Avatar%22%2C%22Lord%20of%20the%20Rings%22%5D%7D&queries[]=%7B%22method%22%3A%22greaterThan%22%2C%22attribute%22%3A%22year%22%2C%22values%22%3A%5B1999%5D%7D HTTP/1.1
|
||||||
|
Content-Type: application/json
|
||||||
|
X-Appwrite-Project: <PROJECT_ID>
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
# Query operators {% #query-operators %}
|
# Query operators {% #query-operators %}
|
||||||
@@ -165,6 +170,9 @@ Query::select(["name", "title"])
|
|||||||
```swift
|
```swift
|
||||||
Query.select(["name", "title"])
|
Query.select(["name", "title"])
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"select","values":["name","title"]}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
## Comparison operators {% #comparison %}
|
## Comparison operators {% #comparison %}
|
||||||
@@ -195,6 +203,9 @@ Query::equal("title", ["Iron Man"])
|
|||||||
```swift
|
```swift
|
||||||
Query.equal("title", value: ["Iron Man"])
|
Query.equal("title", value: ["Iron Man"])
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"equal","attribute":"title","values":["Iron Man"]}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
### Not equal {% #not-equal %}
|
### Not equal {% #not-equal %}
|
||||||
@@ -223,6 +234,9 @@ Query::notEqual("title", ["Iron Man"])
|
|||||||
```swift
|
```swift
|
||||||
Query.notEqual("title", value: ["Iron Man"])
|
Query.notEqual("title", value: ["Iron Man"])
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"notEqual","attribute":"title","values":["Iron Man"]}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
### Less than {% #less-than %}
|
### Less than {% #less-than %}
|
||||||
@@ -251,6 +265,9 @@ Query::lessThan("score", 10)
|
|||||||
```swift
|
```swift
|
||||||
Query.lessThan("score", value: 10)
|
Query.lessThan("score", value: 10)
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"lessThan","attribute":"score","values":[10]}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
### Less than or equal {% #less-than-equal %}
|
### Less than or equal {% #less-than-equal %}
|
||||||
@@ -279,6 +296,9 @@ Query::lessThanEqual("score", 10)
|
|||||||
```swift
|
```swift
|
||||||
Query.lessThanEqual("score", value: 10)
|
Query.lessThanEqual("score", value: 10)
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"lessThanEqual","attribute":"score","values":[10]}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
### Greater than {% #greater-than %}
|
### Greater than {% #greater-than %}
|
||||||
@@ -307,6 +327,9 @@ Query::greaterThan("score", 10)
|
|||||||
```swift
|
```swift
|
||||||
Query.greaterThan("score", value: 10)
|
Query.greaterThan("score", value: 10)
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"greaterThan","attribute":"score","values":[10]}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
### Greater than or equal {% #greater-than-equal %}
|
### Greater than or equal {% #greater-than-equal %}
|
||||||
@@ -335,6 +358,9 @@ Query::greaterThanEqual("score", 10)
|
|||||||
```swift
|
```swift
|
||||||
Query.greaterThanEqual("score", value: 10)
|
Query.greaterThanEqual("score", value: 10)
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"greaterThanEqual","attribute":"score","values":[10]}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
### Between {% #between %}
|
### Between {% #between %}
|
||||||
@@ -363,6 +389,9 @@ Query::between("price", 5, 10)
|
|||||||
```swift
|
```swift
|
||||||
Query.between("price", start: 5, end: 10)
|
Query.between("price", start: 5, end: 10)
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"between","attribute":"price","values":[5,10]}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
## Null checks {% #null-checks %}
|
## Null checks {% #null-checks %}
|
||||||
@@ -393,6 +422,9 @@ Query::isNull("name")
|
|||||||
```swift
|
```swift
|
||||||
Query.isNull("name")
|
Query.isNull("name")
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"isNull","attribute":"name"}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
### Is not null {% #is-not-null %}
|
### Is not null {% #is-not-null %}
|
||||||
@@ -421,6 +453,9 @@ Query::isNotNull("name")
|
|||||||
```swift
|
```swift
|
||||||
Query.isNotNull("name")
|
Query.isNotNull("name")
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"isNotNull","attribute":"name"}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
## String operations {% #string-operations %}
|
## String operations {% #string-operations %}
|
||||||
@@ -451,6 +486,9 @@ Query::startsWith("name", "Once upon a time")
|
|||||||
```swift
|
```swift
|
||||||
Query.startsWith("name", value: "Once upon a time")
|
Query.startsWith("name", value: "Once upon a time")
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"startsWith","attribute":"name","values":["Once upon a time"]}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
### Ends with {% #ends-with %}
|
### Ends with {% #ends-with %}
|
||||||
@@ -479,6 +517,9 @@ Query::endsWith("name", "happily ever after.")
|
|||||||
```swift
|
```swift
|
||||||
Query.endsWith("name", value: "happily ever after.")
|
Query.endsWith("name", value: "happily ever after.")
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"endsWith","attribute":"name","values":["happily ever after."]}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
### Contains {% #contains %}
|
### Contains {% #contains %}
|
||||||
@@ -535,6 +576,13 @@ Query.contains("ingredients", value: ['apple', 'banana'])
|
|||||||
// For strings
|
// For strings
|
||||||
Query.contains("name", value: "Tom")
|
Query.contains("name", value: "Tom")
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
# For arrays
|
||||||
|
{"method":"contains","attribute":"ingredients","values":["apple","banana"]}
|
||||||
|
|
||||||
|
# For strings
|
||||||
|
{"method":"contains","attribute":"name","values":["Tom"]}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
### Search {% #search %}
|
### Search {% #search %}
|
||||||
@@ -563,6 +611,9 @@ Query::search("text", "key words")
|
|||||||
```swift
|
```swift
|
||||||
Query.search("text", value: "key words")
|
Query.search("text", value: "key words")
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"search","attribute":"text","values":["key words"]}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
## Logical operators {% #logical-operators %}
|
## Logical operators {% #logical-operators %}
|
||||||
@@ -614,6 +665,9 @@ Query.and([
|
|||||||
Query.greaterThan("size", value: 5)
|
Query.greaterThan("size", value: 5)
|
||||||
])
|
])
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"and","values":[{"method":"lessThan","attribute":"size","values":[10]},{"method":"greaterThan","attribute":"size","values":[5]}]}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
### OR {% #or %}
|
### OR {% #or %}
|
||||||
@@ -663,6 +717,9 @@ Query.or([
|
|||||||
Query.greaterThan("size", value: 10)
|
Query.greaterThan("size", value: 10)
|
||||||
])
|
])
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"or","values":[{"method":"lessThan","attribute":"size","values":[5]},{"method":"greaterThan","attribute":"size","values":[10]}]}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
## Ordering {% #ordering %}
|
## Ordering {% #ordering %}
|
||||||
@@ -693,6 +750,9 @@ Query::orderDesc("attribute")
|
|||||||
```swift
|
```swift
|
||||||
Query.orderDesc("attribute")
|
Query.orderDesc("attribute")
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"orderDesc","attribute":"attribute"}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
### Order ascending {% #order-asc %}
|
### Order ascending {% #order-asc %}
|
||||||
@@ -721,6 +781,9 @@ Query::orderAsc("attribute")
|
|||||||
```swift
|
```swift
|
||||||
Query.orderAsc("attribute")
|
Query.orderAsc("attribute")
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"orderAsc","attribute":"attribute"}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
## Pagination {% #pagination %}
|
## Pagination {% #pagination %}
|
||||||
@@ -751,6 +814,9 @@ Query::limit(25)
|
|||||||
```swift
|
```swift
|
||||||
Query.limit(25)
|
Query.limit(25)
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"limit","values":[25]}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
### Offset {% #offset %}
|
### Offset {% #offset %}
|
||||||
@@ -779,6 +845,9 @@ Query::offset(0)
|
|||||||
```swift
|
```swift
|
||||||
Query.offset(0)
|
Query.offset(0)
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"offset","values":[0]}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
### Cursor after {% #cursor-after %}
|
### Cursor after {% #cursor-after %}
|
||||||
@@ -807,6 +876,9 @@ Query::cursorAfter("62a7...f620")
|
|||||||
```swift
|
```swift
|
||||||
Query.cursorAfter("62a7...f620")
|
Query.cursorAfter("62a7...f620")
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"cursorAfter","values":["62a7...f620"]}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
### Cursor before {% #cursor-before %}
|
### Cursor before {% #cursor-before %}
|
||||||
@@ -835,6 +907,9 @@ Query::cursorBefore("62a7...a600")
|
|||||||
```swift
|
```swift
|
||||||
Query.cursorBefore("62a7...a600")
|
Query.cursorBefore("62a7...a600")
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"cursorBefore","values":["62a7...a600"]}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
# Complex queries {% #complex-queries %}
|
# Complex queries {% #complex-queries %}
|
||||||
@@ -896,6 +971,9 @@ results = databases.list_documents(
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
```http
|
||||||
|
{"method":"or","values":[{"method":"and","values":[{"method":"equal","attribute":"category","values":["books"]},{"method":"lessThan","attribute":"price","values":[20]}]},{"method":"and","values":[{"method":"equal","attribute":"category","values":["magazines"]},{"method":"lessThan","attribute":"price","values":[10]}]}]}
|
||||||
|
```
|
||||||
{% /multicode %}
|
{% /multicode %}
|
||||||
|
|
||||||
This example demonstrates how to combine `OR` and `AND` operations. The query uses `Query.or()` to match either condition: books under $20 OR magazines under $10.
|
This example demonstrates how to combine `OR` and `AND` operations. The query uses `Query.or()` to match either condition: books under $20 OR magazines under $10.
|
||||||
|
|||||||
Reference in New Issue
Block a user