Merge branch 'master' into chore/swr-to-react-query-1

This commit is contained in:
Alaister Young committed 2022-12-19 12:21:55 +00:00
commit 7331b8cd39
1065 files changed
+187553 -27492

No files matched your search

+38
View File
@@ -0,0 +1,38 @@
name: Mirror Image
on:
workflow_call:
inputs:
version:
required: true
type: string
workflow_dispatch:
inputs:
version:
description: 'Image tag'
required: true
type: string
jobs:
mirror:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: docker/login-action@v2
with:
registry: public.ecr.aws
username: ${{ secrets.PROD_ACCESS_KEY_ID }}
password: ${{ secrets.PROD_SECRET_ACCESS_KEY }}
- uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: akhilerm/tag-push-action@v2.1.0
with:
src: docker.io/supabase/studio:${{ inputs.version }}
dst: |
public.ecr.aws/supabase/studio:${{ inputs.version }}
ghcr.io/supabase/studio:${{ inputs.version }}
+95 -15
View File
@@ -14,25 +14,39 @@ on:
type: string
jobs:
publish:
settings:
runs-on: ubuntu-latest
outputs:
image_version: ${{ steps.meta.outputs.version }}
steps:
- id: meta
uses: docker/metadata-action@v4
with:
images: |
supabase/studio
public.ecr.aws/t3w2s2c9/studio
flavor: |
latest=false
tags: |
type=ref,event=tag
type=sha,prefix={{date 'YYYYMMDD'}},enable=${{ github.ref_type == 'branch' }}
type=raw,value=${{ inputs.version }},enable=${{ github.event_name != 'push' }}
type=sha,prefix={{date 'YYYYMMDD'}}-,enable=${{ github.event_name == 'push' && github.ref_type == 'branch' }}
type=raw,value=${{ inputs.version }},enable=${{ github.event_name == 'workflow_dispatch' }}
- uses: docker/setup-qemu-action@v2
release_x86:
needs: settings
runs-on: ubuntu-latest
timeout-minutes: 120
env:
arch: amd64
outputs:
image_digest: ${{ steps.build.outputs.digest }}
steps:
- id: meta
uses: docker/metadata-action@v4
with:
platforms: amd64,arm64
images: |
supabase/studio
tags: |
type=raw,value=${{ needs.settings.outputs.image_version }}_${{ env.arch }}
- uses: docker/setup-buildx-action@v2
@@ -42,20 +56,86 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to ECR
uses: docker/login-action@v2
with:
registry: public.ecr.aws
username: ${{ secrets.PROD_ACCESS_KEY_ID }}
password: ${{ secrets.PROD_SECRET_ACCESS_KEY }}
- uses: docker/build-push-action@v3
- id: build
uses: docker/build-push-action@v3
with:
push: true
context: '{{defaultContext}}'
file: studio/Dockerfile
target: production
platforms: linux/amd64,linux/arm64
platforms: linux/${{ env.arch }}
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
release_arm:
needs: settings
runs-on: arm-runner
timeout-minutes: 120
env:
arch: arm64
outputs:
image_digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v3
- id: meta
uses: docker/metadata-action@v4
with:
images: |
supabase/studio
tags: |
type=raw,value=${{ needs.settings.outputs.image_version }}_${{ env.arch }}
- uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- uses: docker/setup-buildx-action@v2
with:
driver: docker
driver-opts: |
image=moby/buildkit:master
network=host
- id: build
uses: docker/build-push-action@v3
with:
push: true
context: .
file: studio/Dockerfile
target: production
platforms: linux/${{ env.arch }}
tags: ${{ steps.meta.outputs.tags }}
no-cache: true
merge_manifest:
needs:
- settings
- release_x86
- release_arm
runs-on: ubuntu-latest
steps:
- uses: docker/setup-buildx-action@v2
- uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Merge multi-arch manifests
run: |
docker buildx imagetools create -t supabase/studio:${{ needs.settings.outputs.image_version }} \
supabase/studio@${{ needs.release_x86.outputs.image_digest }} \
supabase/studio@${{ needs.release_arm.outputs.image_digest }}
publish:
needs:
- settings
- merge_manifest
# Call workflow explicitly because events from actions cannot trigger more actions
uses: ./.github/workflows/mirror.yml
with:
version: ${{ needs.settings.outputs.image_version }}
secrets: inherit
+2
View File
@@ -118,3 +118,5 @@ typings/
# Supabase
**/supabase/.branches
**/supabase/.temp
apps/new-docs/*
-1
View File
@@ -1 +0,0 @@
cookie
+14 -7
View File
@@ -8,6 +8,7 @@
- [Running turborepo](#running-turborepo)
- [Shared components](#shared-components)
- [Installing packages](#installing-packages)
- [New Supabase docs](#new-supabase-docs)
3. [Create a pull request](#create-a-pull-request)
- [Common tasks](#common-tasks)
@@ -35,8 +36,6 @@ We are in the process of migrating this repository to monorepo, using [Turborepo
Eventually, all the apps will be run using [Turborepo](https://turborepo.org/docs), which will significantly improve the developer workflow.
If you are working on the ([docs site](https://supabase.com/docs)), please refer to this [developers guide](https://github.com/supabase/supabase/tree/master/apps/reference/DEVELOPERS.md).
### Fork the repository
To contribute code to [Supabase](https://supabase.com), you must fork the [Supabase Repository](https://github.com/supabase/supabase).
@@ -71,11 +70,11 @@ To contribute code to [Supabase](https://supabase.com), you must fork the [Supab
Then visit, and edit, any of the following sites:
| Site | Directory | Scope name | Description | Local development server |
| ---------------------------------------------- | -------------- | ---------- | ------------------------------------ | ------------------------ |
| [supabase.com](https://supabase.com) | `/apps/www` | www | The main website | http://localhost:3000 |
| [app.supabase.com](https://app.supabase.com) | `/studio` | studio | Studio dashboard | http://localhost:8082 |
| [supabase.com/docs](https://supabase.com/docs) | `/apps/docs` | docs | Guides and Reference (Next.js based) | http://localhost:3001 |
| Site | Directory | Scope name | Description | Local development server |
| ---------------------------------------------- | ------------ | ---------- | ------------------------------------ | -------------------------- |
| [supabase.com](https://supabase.com) | `/apps/www` | www | The main website | http://localhost:3000 |
| [app.supabase.com](https://app.supabase.com) | `/studio` | studio | Studio dashboard | http://localhost:8082 |
| [supabase.com/docs](https://supabase.com/docs) | `/apps/docs` | docs | Guides and Reference (Next.js based) | http://localhost:3001/docs |
#### Running sites individually
@@ -108,6 +107,14 @@ For example:
You do not need to install `devDependencies` in each workspace. These can all be installed in the root package.
#### New Supabase docs
Following the changes to the [Supabase docs](https://supabase.com/blog/new-supabase-docs-built-with-nextjs) the following is needed to run the new docs locally:
- Inside of `apps/docs` create a `.env.local` file with the following: `NEXT_PUBLIC_NEW_DOCS=true`
Now when you run a local devlopment docs server you will see the new docs site.
---
## Create a pull request
+1 -1
View File
@@ -58,7 +58,7 @@ Supabase is a combination of open source tools. We’re building the features of
Supabase is a [hosted platform](https://app.supabase.com). You can sign up and start using Supabase without installing anything.
You can also [self-host](https://supabase.com/docs/guides/hosting/overview) and [develop locally](https://supabase.com/docs/guides/local-development).
![Architecture](https://user-images.githubusercontent.com/70828596/187547862-ffa9d058-0c3a-4851-a3e7-92ccfca4b596.png)
![Architecture](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.png)
- [PostgreSQL](https://www.postgresql.org/) is an object-relational database system with over 30 years of active development that has earned it a strong reputation for reliability, feature robustness, and performance.
- [Realtime](https://github.com/supabase/realtime) is an Elixir server that allows you to listen to PostgreSQL inserts, updates, and deletes using websockets. Realtime polls Postgres' built-in replication functionality for database changes, converts changes to JSON, then broadcasts the JSON over websockets to authorized clients.
+372
View File
@@ -0,0 +1,372 @@
module.exports = {
name: 'Stripe Docs Blue',
type: 'dark',
colors: {
'editor.background': '#232323',
'editor.foreground': '#fafafa',
'activityBar.background': 'var(--colors-scale2)',
'sideBar.background': 'yellow',
'editorGroupHeader.tabsBackground': 'var(--colors-scale2)',
'sideBarSectionHeader.background': 'var(--colors-scale2)',
'tab.activeBackground': 'var(--colors-scale3)',
'tab.inactiveBackground': 'var(--colors-scale2)',
'tab.border': 'var(--colors-scale2)',
'input.background': '#ffffff1a',
'panel.background': '#1A2652',
'panel.border': '#1A2652',
'editorWidget.background': '#0d0f2b',
'editorWidget.foreground': '#ffffff4d',
'editorWidget.border': 'var(--colors-scale5)',
'list.hoverBackground': '#ffffff1a',
'list.activeSelectionBackground': '#ffffff1a',
'list.inactiveSelectionBackground': '#ffffff1a',
'editor.hoverHighlightBackground': '#ffffff1a',
'editor.selectionHighlightBackground': '#ffffff1a',
'activityBarBadge.background': 'yellow',
'sideBarTitle.foreground': 'var(--colors-scale2)',
'statusBar.background': 'var(--colors-scale2)',
},
tokenColors: [
{
name: 'Comment',
scope: ['comment', 'punctuation.definition.comment'],
settings: {
foreground: '#a3acb9',
fontStyle: '',
},
},
{
name: 'Variables',
scope: ['source', 'variable', 'variable.other.object', 'string constant.other.placeholder'],
settings: {
foreground: '#f5fbff',
},
},
{
name: 'Colors',
scope: ['variable.other.constant', 'constant.other.color'],
settings: {
foreground: '#ffffff',
fontStyle: 'bold',
},
},
{
name: 'Invalid',
scope: ['invalid', 'invalid.illegal'],
settings: {
foreground: '#FF5370',
},
},
{
name: 'Keyword, Storage',
scope: ['keyword', 'storage.type', 'storage.modifier'],
settings: {
foreground: '#98C1FE',
fontStyle: 'bold',
},
},
{
name: 'Function',
scope: ['entity.name.function'],
settings: {
foreground: '#7fd3ed',
fontStyle: 'bold',
},
},
{
name: 'Tag',
scope: ['entity.name.tag', 'meta.tag.sgml', 'markup.deleted.git_gutter'],
settings: {
foreground: '#98C1FE',
fontStyle: 'bold',
},
},
{
name: 'Parameter, Property',
scope: [
'variable.parameter',
'variable.other.object.property',
'variable.other.property',
'keyword.other.unit',
'keyword.other',
],
settings: {
foreground: '#F2AFE3',
},
},
{
name: 'Number, Constant, Function Argument, Tag Attribute, Embedded',
scope: [
'constant.numeric',
'constant.language',
'support.constant',
'constant.character',
'constant.escape',
],
settings: {
foreground: '#f8b886',
},
},
{
name: 'String, Symbols, Inherited Class, Markup Heading',
scope: [
'string',
'constant.other.symbol',
'constant.other.key',
'entity.other.inherited-class',
'markup.heading',
'markup.inserted.git_gutter',
'meta.group.braces.curly constant.other.object.key.js string.unquoted.label.js',
],
settings: {
foreground: '#85d99e',
},
},
{
name: 'Entity Types',
scope: ['support.type'],
settings: {
foreground: '#B2CCD6',
},
},
{
name: 'CSS Class and Support',
scope: [
'source.css support.type.property-name',
'source.sass support.type.property-name',
'source.scss support.type.property-name',
'source.less support.type.property-name',
'source.stylus support.type.property-name',
'source.postcss support.type.property-name',
],
settings: {
foreground: '#B2CCD6',
},
},
{
name: 'Language methods',
scope: ['variable.language'],
settings: {
fontStyle: 'italic',
foreground: '#FF5370',
},
},
{
name: 'Attributes',
scope: ['entity.other.attribute-name'],
settings: {
foreground: '#98C1FE',
fontStyle: 'italic',
},
},
{
name: 'Inserted',
scope: ['markup.inserted'],
settings: {
foreground: '#C3E88D',
},
},
{
name: 'Deleted',
scope: ['markup.deleted'],
settings: {
foreground: '#FF5370',
},
},
{
name: 'Changed',
scope: ['markup.changed'],
settings: {
foreground: '#C792EA',
},
},
{
name: 'Regular Expressions',
scope: ['string.regexp'],
settings: {
foreground: '#89DDFF',
},
},
{
name: 'Escape Characters',
scope: ['constant.character.escape'],
settings: {
foreground: '#89DDFF',
},
},
{
name: 'URL',
scope: ['*url*', '*link*', '*uri*'],
settings: {
fontStyle: 'underline',
},
},
{
name: 'ES7 Bind Operator',
scope: ['source.js constant.other.object.key.js string.unquoted.label.js'],
settings: {
fontStyle: 'italic',
foreground: '#FF5370',
},
},
{
name: 'Markdown - Plain',
scope: ['text.html', 'punctuation.definition.list_item'],
settings: {
foreground: '#f5fbff',
},
},
{
name: 'Markdown - Markup Raw Inline',
scope: ['text.html.markdown markup.inline.raw.markdown'],
settings: {
foreground: '#C792EA',
},
},
{
name: 'Markdown - Markup Raw Inline Punctuation',
scope: ['text.html.markdown markup.inline.raw.markdown punctuation.definition.raw.markdown'],
settings: {
foreground: '#65737E',
},
},
{
name: 'Markdown - Heading',
scope: [
'markdown.heading',
'markup.heading | markup.heading entity.name',
'markup.heading.markdown punctuation.definition.heading.markdown',
],
settings: {
foreground: '#C3E88D',
},
},
{
name: 'Markup - Italic',
scope: ['markup.italic'],
settings: {
fontStyle: 'italic',
foreground: '#f07178',
},
},
{
name: 'Markup - Bold',
scope: ['markup.bold', 'markup.bold string'],
settings: {
fontStyle: 'bold',
foreground: '#f07178',
},
},
{
name: 'Markup - Bold-Italic',
scope: [
'markup.bold markup.italic',
'markup.italic markup.bold',
'markup.quote markup.bold',
'markup.bold markup.italic string',
'markup.italic markup.bold string',
'markup.quote markup.bold string',
],
settings: {
fontStyle: 'bold',
foreground: '#f07178',
},
},
{
name: 'Markup - Underline',
scope: ['markup.underline'],
settings: {
fontStyle: 'underline',
foreground: '#F78C6C',
},
},
{
name: 'Markdown - Blockquote',
scope: ['markup.quote punctuation.definition.blockquote.markdown'],
settings: {
foreground: '#65737E',
},
},
{
name: 'Markup - Quote',
scope: ['markup.quote'],
settings: {
fontStyle: 'italic',
},
},
{
name: 'Markdown - Link Description',
scope: ['string.other.link.description.title.markdown'],
settings: {
foreground: '#C792EA',
},
},
{
name: 'Markdown - Link Anchor',
scope: ['constant.other.reference.link.markdown'],
settings: {
foreground: '#FFCB6B',
},
},
{
name: 'Markup - Raw Block',
scope: ['markup.raw.block'],
settings: {
foreground: '#C792EA',
},
},
{
name: 'Markdown - Raw Block Fenced',
scope: ['markup.raw.block.fenced.markdown'],
settings: {
foreground: '#00000050',
},
},
{
name: 'Markdown - Fenced Bode Block',
scope: ['punctuation.definition.fenced.markdown'],
settings: {
foreground: '#00000050',
},
},
{
name: 'Markdown - Fenced Bode Block Variable',
scope: [
'markup.raw.block.fenced.markdown',
'variable.language.fenced.markdown',
'punctuation.section.class.end',
],
settings: {
foreground: '#EEFFFF',
},
},
{
name: 'Markdown - Fenced Language',
scope: ['variable.language.fenced.markdown'],
settings: {
foreground: '#65737E',
},
},
{
name: 'Markdown - Separator',
scope: ['meta.separator'],
settings: {
fontStyle: 'bold',
foreground: '#65737E',
},
},
{
name: 'Markup - Table',
scope: ['markup.table'],
settings: {
foreground: '#EEFFFF',
},
},
],
}
+2 -2
View File
@@ -25,7 +25,7 @@ const ButtonCard: FC<Props> = ({
className={[
'h-full block shadow-none bg-scale-300 rounded transition',
'border border-transparent hover:border-scale-600',
].join('')}
].join(' ')}
>
{children ? (
children
@@ -43,7 +43,7 @@ const ButtonCard: FC<Props> = ({
) : (
icon
)}
<h3 className="my-0 text-base">{title}</h3>
<h3 className="my-0 text-base text-scale-1200">{title}</h3>
<p className="my-0 text-sm">{description}</p>
</div>
)}
+76 -63
View File
@@ -10,13 +10,14 @@ import py from 'react-syntax-highlighter/dist/cjs/languages/hljs/python'
import sql from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql'
import bash from 'react-syntax-highlighter/dist/cjs/languages/hljs/bash'
import dart from 'react-syntax-highlighter/dist/cjs/languages/hljs/dart'
import json from 'react-syntax-highlighter/dist/cjs/languages/hljs/json'
import { useState } from 'react'
import { useTheme } from '../Providers'
import { useTheme } from 'common/Providers'
interface Props {
title?: string
language: 'js' | 'jsx' | 'sql' | 'py' | 'bash' | 'ts' | 'dart'
language: 'js' | 'jsx' | 'sql' | 'py' | 'bash' | 'ts' | 'dart' | 'json'
linesToHighlight?: number[]
hideCopy?: boolean
hideLineNumbers?: boolean
@@ -47,6 +48,11 @@ const CodeBlock: FC<Props> = ({
}, 1000)
}
// check the length of the string inside the <code> tag
// if it's fewer than 70 characters, add a white-space: pre so it doesn't wrap
const shortCodeBlockClasses =
typeof children === 'string' && children.length < 70 ? 'short-inline-codeblock' : ''
let lang = language ? language : className ? className.replace('language-', '') : 'js'
// force jsx to be js highlighted
if (lang === 'jsx') lang = 'js'
@@ -56,81 +62,88 @@ const CodeBlock: FC<Props> = ({
SyntaxHighlighter.registerLanguage('sql', sql)
SyntaxHighlighter.registerLanguage('bash', bash)
SyntaxHighlighter.registerLanguage('dart', dart)
SyntaxHighlighter.registerLanguage('json', json)
const large = false
// don't show line numbers if bash == lang
const showLineNumbers = hideLineNumbers || lang !== 'bash'
if (lang !== 'bash') hideLineNumbers = true
const showLineNumbers = !hideLineNumbers
return (
<div className="relative my-2 max-w-[90vw] md:max-w-none overflow-auto">
<>
{title && (
<div className="rounded-t-md bg-scale-300 py-2 px-4 border-b border-scale-500 text-blue-1100 font-sans">
{title.replace(/%20/g, ' ')}
</div>
)}
{className ? (
<SyntaxHighlighter
language={lang}
wrapLines={true}
style={monokaiTheme}
className={[
'code-block border p-4 w-full !my-0 !bg-scale-300',
`${!title ? '!rounded-md' : '!rounded-t-none !rounded-b-md'}`,
`${!showLineNumbers ? 'pl-6' : ''}`,
].join(' ')}
customStyle={{
fontSize: large ? 18 : 13,
lineHeight: large ? 1.5 : 1.4,
}}
showLineNumbers={showLineNumbers}
lineProps={(lineNumber) => {
if (linesToHighlight.includes(lineNumber)) {
return {
style: { display: 'block', backgroundColor: 'var(--colors-scale6)' },
<div className="relative max-w-[90vw] md:max-w-none overflow-auto">
<SyntaxHighlighter
language={lang}
wrapLines={true}
style={monokaiTheme}
className={[
'code-block border p-4 w-full !my-0 !bg-scale-300',
`${!title ? '!rounded-md' : '!rounded-t-none !rounded-b-md'}`,
`${!showLineNumbers ? 'pl-6' : ''}`,
className,
].join(' ')}
customStyle={{
fontSize: large ? 18 : 13,
lineHeight: large ? 1.5 : 1.4,
}}
showLineNumbers={showLineNumbers}
lineProps={(lineNumber) => {
if (linesToHighlight.includes(lineNumber)) {
return {
style: { display: 'block', backgroundColor: 'var(--colors-scale6)' },
}
}
}
return {}
}}
lineNumberContainerStyle={{
paddingTop: '128px',
}}
lineNumberStyle={{
minWidth: '44px',
paddingLeft: '4px',
paddingRight: '4px',
marginRight: '12px',
color: '#828282',
textAlign: 'center',
fontSize: large ? 14 : 12,
paddingTop: '4px',
paddingBottom: '4px',
}}
>
{(value || children)?.trimEnd()}
</SyntaxHighlighter>
) : (
<code>{value || children}</code>
)}
{!hideCopy && (value || children) && className ? (
<div
className={[
'absolute right-2',
`${isDarkMode ? 'dark' : ''}`,
`${!title ? 'top-2' : 'top-[3.25rem]'}`,
].join(' ')}
>
<CopyToClipboard text={value || children}>
<Button
type="default"
icon={copied ? <IconCheck /> : <IconCopy />}
onClick={() => handleCopy()}
return {}
}}
lineNumberContainerStyle={{
paddingTop: '128px',
}}
lineNumberStyle={{
minWidth: '44px',
paddingLeft: '4px',
paddingRight: '4px',
marginRight: '12px',
color: '#828282',
textAlign: 'center',
fontSize: large ? 14 : 12,
paddingTop: '4px',
paddingBottom: '4px',
}}
>
{(value || children)?.trimEnd()}
</SyntaxHighlighter>
{!hideCopy && (value || children) && className ? (
<div
className={[
'absolute right-2',
`${isDarkMode ? 'dark' : ''}`,
`${!title ? 'top-2' : 'top-[3.25rem]'}`,
].join(' ')}
>
{copied ? 'Copied' : ''}
</Button>
</CopyToClipboard>
{/* //
@ts-ignore */}
<CopyToClipboard text={value || children}>
<Button
type="default"
icon={copied ? <IconCheck /> : <IconCopy />}
onClick={() => handleCopy()}
>
{copied ? 'Copied' : ''}
</Button>
</CopyToClipboard>
</div>
) : null}
</div>
) : null}
</div>
) : (
<code className={shortCodeBlockClasses}>{value || children}</code>
)}
</>
)
}
+22
View File
@@ -0,0 +1,22 @@
import { FC } from 'react'
import { IconSearch } from '~/../../packages/ui'
// [Terry] — delete this after development
// just for making it easy to see where things are coming from
interface Props {
link: string
text: string
className?: string
}
export const ComesFrom: FC<Props> = ({ link, text, className }) => {
return (
<div className={`comes-from text-xs flex items-center gap-2 -mb-6 ${className}`}>
<span className="uppercase bg-slate-100 p-1 text-slate-600 flex gap-2 items-center">
<IconSearch size={14} />
Comes from:
</span>
<a className="text-slate-600 no-underline hover:underline" href={link}>
{text}
</a>
</div>
)
}
@@ -77,3 +77,19 @@ export const unHighlightSelectedTocItems = () => {
const currentActiveItem = document.querySelector('.toc-menu .toc__menu-item--active')
currentActiveItem?.classList.remove('toc__menu-item--active')
}
export const highlightSelectedNavItem = (id: string) => {
const navMenuItems = document.querySelectorAll('.function-link-item a')
// find any currently active items and remove them
const currentActiveItems = document.querySelectorAll('.function-link-list .text-brand-900')
currentActiveItems.forEach((item) => item.classList.remove('text-brand-900'))
// Add active class to the current item
navMenuItems.forEach((item) => {
// @ts-ignore
if (item.href.split('/').at(-1) === id) {
item.classList.add('text-brand-900')
}
})
}
@@ -6,16 +6,25 @@ import {
} from './CustomHTMLElements.utils'
import { useInView } from 'react-intersection-observer'
const Heading = ({ tag, children }) => {
/**
* [Joshen] The trick with rootMargin
* We are shrinking the top of the root element by 20 percent, which is currently our entire page,
* and the bottom by 35 percent. Therefore, when a header is at the top 20 percent and bottom 35 percent
* of our page, it will not be counted as visible.
*/
interface Props {
tag: string
}
const Heading: React.FC<Props> = ({ tag, children }) => {
const HeadingTag = `${tag}` as any
const anchor = getAnchor(children)
const link = `#${anchor}`
// check if current heading is in view, update TOC active item accordingly
// [Joshen] Ideally we highlight the section in the TOC when it's at the top of the page
// much like when we click on the item in the TOC itself, the rootMargin fix is insufficient
const { ref } = useInView({
threshold: 1,
rootMargin: '-20% 0% -35% 0px',
onChange: (inView, entry) => {
if (window.scrollY === 0) unHighlightSelectedTocItems()
if (inView) highlightSelectedTocItem(entry.target.id)
@@ -1,11 +1,13 @@
const InlineCodeTag = ({ children }) => {
import { FC } from 'react'
const InlineCodeTag = ({ children }: any) => {
// If children isn't a string, just return it as is, just in case
if (typeof children !== 'string') return children
// check the length of the string inside the <code> tag
// if it's fewer than 70 characters, add a white-space: pre so it doesn't wrap
const classes = children.length < 70 ? 'short-inline-codeblock' : ''
const className = children.length < 70 ? 'short-inline-codeblock' : ''
return <code className={classes}>{children}</code>
return <code className={className}> HELLO WORLD {children}</code>
}
export default InlineCodeTag
+4 -2
View File
@@ -20,7 +20,6 @@ export function SearchProvider({ children }: any) {
const router = useRouter()
const [isOpen, setIsOpen] = useState(false)
const [initialQuery, setInitialQuery] = useState(null)
const onOpen = useCallback(() => {
setIsOpen(true)
}, [setIsOpen])
@@ -85,6 +84,7 @@ export function SearchProvider({ children }: any) {
hitComponent={Hit}
transformItems={(items) => {
return items.map((item, index) => {
console.log('item', item)
// We transform the absolute URL into a relative URL to
// leverage Next's preloading.
const a = document.createElement('a')
@@ -96,7 +96,8 @@ export function SearchProvider({ children }: any) {
...item,
url: `${a.pathname}${hash}`,
__is_result: () => true,
__is_parent: () => item.type === 'lvl1' && items.length > 1 && index === 0,
__is_parent: () => item.type === 'lvl1' && index === 0,
__is_guide: () => item.hierarchy.lvl0 === 'Guides',
__is_child: () =>
item.type !== 'lvl1' &&
items.length > 1 &&
@@ -125,6 +126,7 @@ function Hit({ hit, children }) {
'DocSearch-Hit--FirstChild': hit.__is_first?.(),
'DocSearch-Hit--LastChild': hit.__is_last?.(),
'DocSearch-Hit--Child': hit.__is_child?.(),
'DocSearch-Hit--Guide': hit.__is_guide?.(),
})}
>
{children}
@@ -1,4 +1,4 @@
import { useTheme } from '~/components/Providers'
import { useTheme } from 'common/Providers'
import { IconSun, IconMoon } from '@supabase/ui'
function DarkModeToggle() {
@@ -19,7 +19,7 @@ function DarkModeToggle() {
type="button"
aria-pressed="false"
className={`
relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent
relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent
transition-colors duration-200 ease-in-out focus:outline-none ${
isDarkMode ? 'bg-scale-500' : 'bg-scale-900'
} mx-5
+1 -1
View File
@@ -3,7 +3,7 @@ import Link from 'next/link'
import Image from 'next/image'
import FooterLinks from 'data/footer.json'
import DarkModeToggle from './DarkModeToggle'
import { useTheme } from '../Providers'
import { useTheme } from 'common/Providers'
import * as supabaseLogoWordmarkDark from 'common/assets/images/supabase-logo-wordmark--dark.png'
import * as supabaseLogoWordmarkLight from 'common/assets/images/supabase-logo-wordmark--light.png'
@@ -0,0 +1,13 @@
const FooterHelpCallout = () => {
return (
<div className="min-w-full px-8 py-6 prose bg-scale-300 dark:bg-whiteA-200 rounded">
<h5 className="text-sm m-0 text-scale-1100">Need some help?</h5>
<p className="m-0 text-sm text-scale-900">
Not to worry, our specialist engineers are here to help. Submit a support ticket through the{' '}
<a href="https://app.supabase.com/support/new">Dashboard</a>.
</p>
</div>
)
}
export default FooterHelpCallout
+22 -22
View File
@@ -1,5 +1,5 @@
import ButtonCard from './ButtonCard'
import { useTheme } from '~/components/Providers'
import { useTheme } from 'common/Providers'
const Frameworks = () => {
const { isDarkMode } = useTheme()
@@ -8,86 +8,86 @@ const Frameworks = () => {
{
name: 'Angular',
logo: {
light: '/docs/img/libraries/angular-icon.svg',
dark: '/docs/img/libraries/angular-icon.svg',
light: '/docs/img/icons/angular-icon.svg',
dark: '/docs/img/icons/angular-icon.svg',
},
href: '/guides/with-angular',
},
{
name: 'Expo',
logo: {
light: '/docs/img/libraries/expo-icon.svg',
dark: '/docs/img/libraries/expo-icon-dark.svg',
light: '/docs/img/icons/expo-icon.svg',
dark: '/docs/img/icons/expo-icon-dark.svg',
},
href: '/guides/with-expo',
},
{
name: 'Flutter',
logo: {
light: '/docs/img/libraries/flutter-icon.svg',
dark: '/docs/img/libraries/flutter-icon.svg',
light: '/docs/img/icons/flutter-icon.svg',
dark: '/docs/img/icons/flutter-icon.svg',
},
href: '/guides/with-flutter',
},
{
name: 'JavaScript',
logo: {
light: '/docs/img/libraries/javascript-icon.svg',
dark: '/docs/img/libraries/javascript-icon.svg',
light: '/docs/img/icons/javascript-icon.svg',
dark: '/docs/img/icons/javascript-icon.svg',
},
href: '/reference/javascript/installing#javascript',
},
{
name: 'Next.js',
logo: {
light: '/docs/img/libraries/nextjs-light-icon.svg',
dark: '/docs/img/libraries/nextjs-dark-icon.svg',
light: '/docs/img/icons/nextjs-light-icon.svg',
dark: '/docs/img/icons/nextjs-dark-icon.svg',
},
href: '/guides/with-nextjs',
},
{
name: 'React',
logo: {
light: '/docs/img/libraries/react-icon.svg',
dark: '/docs/img/libraries/react-icon.svg',
light: '/docs/img/icons/react-icon.svg',
dark: '/docs/img/icons/react-icon.svg',
},
href: '/guides/with-react',
},
{
name: 'RedwoodJS',
logo: {
light: '/docs/img/libraries/redwoodjs-icon.svg',
dark: '/docs/img/libraries/redwoodjs-icon.svg',
light: '/docs/img/icons/redwoodjs-icon.svg',
dark: '/docs/img/icons/redwoodjs-icon.svg',
},
href: '/guides/with-redwoodjs',
},
{
name: 'SolidJS',
logo: {
light: '/docs/img/libraries/solidjs-icon.svg',
dark: '/docs/img/libraries/solidjs-icon.svg',
light: '/docs/img/icons/solidjs-icon.svg',
dark: '/docs/img/icons/solidjs-icon.svg',
},
href: '/guides/with-solidjs',
},
{
name: 'Svelte',
logo: {
light: '/docs/img/libraries/svelte-icon.svg',
dark: '/docs/img/libraries/svelte-icon.svg',
light: '/docs/img/icons/svelte-icon.svg',
dark: '/docs/img/icons/svelte-icon.svg',
},
href: '/guides/with-svelte',
},
{
name: 'Vue',
logo: {
light: '/docs/img/libraries/vuejs-icon.svg',
dark: '/docs/img/libraries/vuejs-icon.svg',
light: '/docs/img/icons/vuejs-icon.svg',
dark: '/docs/img/icons/vuejs-icon.svg',
},
href: '/guides/with-vue-3',
},
]
return (
<div className="grid md:grid-cols-12 gap-4">
<div className="grid md:grid-cols-12 gap-4 not-prose">
{frameworks.map((x) => (
<div key={x.name} className="col-span-3">
<ButtonCard
+3 -4
View File
@@ -33,10 +33,9 @@ const examples = [
href: 'https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/read-storage/index.ts',
},
{
name: 'OpenGraph Image Generation',
description:
'Generate Open Graph images with Deno and Supabase Edge Functions, no framework needed.',
href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/opengraph',
name: 'Open Graph Image Generation',
description: 'Generate Open Graph images with Deno and Supabase Edge Functions.',
href: '/guides/functions/examples/og-image',
},
{
name: 'OG Image Generation & Storage CDN Caching',
+1 -17
View File
@@ -41,7 +41,7 @@ interface Props {
const GuidesTableOfContents: FC<Props> = ({ list }) => {
return (
<ul className="toc-menu list-none pl-4 text-[0.8rem] grid gap-2 mt-12">
<ul className="toc-menu list-none pl-5 text-[0.8rem] grid gap-2">
{list.map((item, i) => (
<li key={`${item.level}-${i}`} className={item.level === 3 ? 'ml-4' : ''}>
<a
@@ -52,22 +52,6 @@ const GuidesTableOfContents: FC<Props> = ({ list }) => {
</li>
))}
</ul>
// <ul className="toc-menu list-none pl-4 text-[0.8rem] grid gap-2 mt-12">
// {(toc.json as TOCHeader[])
// .filter((item) => item.lvl !== 1 && item.lvl <= 3)
// .map((item: any, i: number) => {
// return (
// <li key={i} id={item.lvl} style={{ marginLeft: `${(item.lvl - 2) * 1}rem` }}>
// <a
// href={`#${formatSlug(item.slug)}`}
// className="text-scale-1000 hover:text-brand-900 transition-colors"
// dangerouslySetInnerHTML={{ __html: formatTOCHeader(removeAnchor(item.content)) }}
// />
// </li>
// )
// })}
// </ul>
)
}
+6 -4
View File
@@ -1,18 +1,20 @@
import UserManagementSQLTemplate from './user_management_quickstart_sql_template.mdx'
import { Tabs } from 'ui'
export const TabPanel = Tabs.Panel
## Project set up
# Project set up
Before we start building we're going to set up our Database and API. This is as simple as starting a new Project in Supabase
and then creating a "schema" inside the database.
### Create a project
## Create a project
1. Go to [app.supabase.com](https://app.supabase.com).
1. Click on "New Project".
1. Enter your project details.
1. Wait for the new database to launch.
### Set up the database schema
## Set up the database schema
Now we are going to set up the database schema. We can use the "User Management Starter" quickstart in the SQL Editor,
or you can just copy/paste the SQL from below and run it yourself.
@@ -41,7 +43,7 @@ or you can just copy/paste the SQL from below and run it yourself.
</TabPanel>
</Tabs>
### Get the API Keys
## Get the API Keys
Now that you've created some database tables, you are ready to insert data using the auto-generated API.
We just need to get the URL and `anon` key from the API settings.
@@ -1,5 +1,3 @@
## Intro
This example provides the steps to build a basic user management app. It includes:
- Supabase [Database](/docs/guides/database): a Postgres database for storing your user data.
@@ -86,7 +86,7 @@ const AlgoliaSearch: FC<Props> = ({}) => {
},
},
onSelect({ item, setQuery, setIsOpen, refresh }) {
console.log('onSelect', item.url)
//console.log('onSelect', item.url)
},
// @ts-ignore
getSources({ query }) {
+6 -6
View File
@@ -3,15 +3,13 @@ import Link from 'next/link'
import { useRouter } from 'next/router'
import { useState, useEffect, FC } from 'react'
import { IconMenu, IconMoon, IconSearch, IconSun, IconCommand, Listbox } from 'ui'
import { useTheme } from '../Providers'
import { useTheme } from 'common/Providers'
import { REFERENCES } from './Navigation.constants'
import { SearchButton } from '../DocSearch'
interface Props {
currentPage: string
}
import { getPageType } from '~/lib/helpers'
const NavBar: FC<Props> = ({ currentPage }) => {
const NavBar: FC = () => {
const { isDarkMode, toggleTheme } = useTheme()
const [mounted, setMounted] = useState(false)
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
@@ -27,6 +25,8 @@ const NavBar: FC<Props> = ({ currentPage }) => {
? pathSegments[pathSegments.indexOf(library) + 1]
: versions[0]
const pageType = getPageType(asPath)
useEffect(() => {
setMounted(true)
}, [isDarkMode])
@@ -96,7 +96,7 @@ const NavBar: FC<Props> = ({ currentPage }) => {
<Link href={p.link}>
<a
className={`text-sm ${
currentPage.includes(p.key) ? 'text-brand-900' : 'text-scale-1100'
pageType.includes(p.key) ? 'text-brand-900' : 'text-scale-1100'
}`}
>
{p.text}
@@ -17,13 +17,13 @@ export const REFERENCES: References = {
name: 'supabase-js',
library: 'supabase-js',
versions: ['v2', 'v1'],
icon: '/docs/img/libraries/javascript-icon.svg',
icon: '/docs/img/icons/javascript-icon.svg',
},
dart: {
name: 'Flutter',
library: 'supabase-dart',
versions: ['v1', 'v0'],
icon: '/docs/img/libraries/flutter-icon.svg',
icon: '/docs/img/icons/flutter-icon.svg',
},
cli: {
name: 'CLI',
@@ -102,6 +102,7 @@ export const menuItems: NavMenu = {
{ name: 'Login with Twitch', url: '/guides/auth/auth-twitch', items: [] },
{ name: 'Login with Twitter', url: '/guides/auth/auth-twitter', items: [] },
{ name: 'Login with WorkOS', url: '/guides/auth/auth-workos', items: [] },
{ name: 'Login with Zoom', url: '/guides/auth/auth-zoom', items: [] },
{ name: 'Phone Auth with Twilio', url: '/guides/auth/auth-twilio', items: [] },
{ name: 'Phone Auth with Vonage', url: '/guides/auth/auth-vonage', items: [] },
{
@@ -226,8 +227,9 @@ export const menuItems: NavMenu = {
label: 'Edge Functions',
items: [
{ name: 'Overview', url: '/guides/functions', items: [] },
{ name: 'Quickstart', url: '/guides/functions/quickstart', items: [] },
{ name: 'Auth', url: '/guides/functions/auth', items: [] },
{ name: 'Examples', url: '/guides/functions/examples', items: [] },
{ name: 'Best Practices', url: '/guides/functions/best-practices', items: [] },
{ name: 'CI/CD Workflow', url: '/guides/functions/cicd-workflow', items: [] },
],
},
@@ -236,7 +238,7 @@ export const menuItems: NavMenu = {
items: [
{ name: 'Overview', url: '/guides/realtime', items: [] },
{ name: 'Quickstart', url: '/guides/realtime/quickstart', items: [] },
{ name: 'Postgres CDC', url: '/guides/realtime/postgres-cdc', items: [] },
{ name: 'Postgres Changes', url: '/guides/realtime/postgres-changes', items: [] },
{ name: 'Rate Limits', url: '/guides/realtime/rate-limits', items: [] },
],
},
@@ -244,13 +246,16 @@ export const menuItems: NavMenu = {
label: 'Storage',
items: [
{ name: 'Overview', url: '/guides/storage', items: [] },
{ name: 'CDN', url: '/guides/storage-cdn', items: [] },
{ name: 'Quickstart', url: '/guides/storage/quickstart', items: [] },
{ name: 'Access Control', url: '/guides/storage/access-control', items: [] },
{ name: 'CDN', url: '/guides/storage/cdn', items: [] },
{ name: 'Image Transformations', url: '/guides/storage/image-transformations', items: [] },
],
},
{
label: 'Platform',
items: [
{ name: 'Overview', url: '/guides/hosting/platform', items: [] },
{ name: 'Overview', url: '/guides/platform', items: [] },
{ name: 'Compute Add-ons', url: '/guides/platform/compute-add-ons', items: [] },
{ name: 'Custom Domains', url: '/guides/platform/custom-domains', items: [] },
{ name: 'Database Usage', url: '/guides/platform/database-usage', items: [] },
@@ -261,6 +266,7 @@ export const menuItems: NavMenu = {
url: '/guides/platform/migrating-and-upgrading-projects',
items: [],
},
{ name: 'Network Restrictions', url: '/guides/platform/network-restrictions', items: [] },
{ name: 'Performance Tuning', url: '/guides/platform/performance', items: [] },
{ name: 'Permissions', url: '/guides/platform/permissions', items: [] },
{ name: 'Production Readiness', url: '/guides/platform/going-into-prod', items: [] },
@@ -313,6 +319,7 @@ export const menuItems: NavMenu = {
{ name: 'Sequin', url: '/guides/integrations/sequin', items: [] },
{ name: 'Snaplet', url: '/guides/integrations/snaplet', items: [] },
{ name: 'Vercel', url: '/guides/integrations/vercel', items: [] },
{ name: 'Zuplo', url: '/guides/integrations/zuplo', items: [] },
],
},
{
@@ -19,5 +19,6 @@ export interface References {
library?: string
versions: string[]
icon: string
currentVersion?: string
}
}
@@ -0,0 +1,181 @@
import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useMenuLevelId } from '~/hooks/useMenuState'
import { useTheme } from 'common/Providers'
const home = [
[
{
label: 'Home',
icon: '/img/icons/menu/home',
href: '/',
level: 'home',
},
{
label: 'Getting Started',
icon: '/img/icons/menu/getting-started',
href: '/guides/getting-started',
level: 'gettingstarted',
},
],
[
{
label: 'Database',
icon: '/img/icons/menu/database',
href: '/guides/database',
level: 'database',
},
{
label: 'Auth',
icon: '/img/icons/menu/auth',
href: '/guides/auth/overview',
level: 'auth',
},
{
label: 'Edge Functions',
icon: '/img/icons/menu/functions',
href: '/guides/functions',
level: 'functions',
},
{
label: 'Realtime',
icon: '/img/icons/menu/realtime',
href: '/guides/realtime',
level: 'realtime',
},
{
label: 'Storage',
icon: '/img/icons/menu/storage',
href: '/guides/storage',
level: 'storage',
},
],
[
{
label: 'Platform',
icon: '/img/icons/menu/platform',
href: '/guides/platform',
level: 'platform',
},
{
label: 'Resources',
icon: '/img/icons/menu/platform',
href: '/guides/resources',
level: 'resources',
},
{
label: 'Integrations',
icon: '/img/icons/menu/integrations',
hasLightIcon: true,
href: '/guides/integrations',
level: 'integrations',
},
],
[
{
label: 'Client Library Reference',
},
{
label: 'JavaScript',
icon: '/img/icons/menu/reference-javascript',
href: '/reference/javascript/introduction',
level: 'reference_javascript',
},
{
label: 'Flutter',
icon: '/img/icons/menu/reference-dart',
href: '/reference/dart/introduction',
level: 'reference_dart',
},
{
label: 'Tools Reference',
},
{
label: 'Management API',
icon: '/img/icons/menu/reference-api',
href: '/reference/api/introduction',
level: 'reference_javascript',
},
{
label: 'Supabase CLI',
icon: '/img/icons/menu/reference-cli',
href: '/reference/cli/introduction',
level: 'reference_javascript',
},
],
]
const NavigationMenuHome = () => {
const level = useMenuLevelId()
const router = useRouter()
const { isDarkMode } = useTheme()
return (
<div
className={[
'transition-all duration-150 ease-out',
level === 'home' || !level
? 'opacity-100 ml-0 delay-150'
: 'opacity-0 -ml-8 invisible absolute',
].join(' ')}
>
<ul className="relative w-full flex flex-col gap-4">
{home.map((section, sectionIndex) => {
return (
<>
{sectionIndex !== 0 && (
<div
className="h-px w-full bg-blackA-300 dark:bg-whiteA-300"
key={`section-${sectionIndex}-border`}
></div>
)}
<div key={`section-${sectionIndex}`}>
<div className="flex flex-col gap-3">
{section.map((link) => {
if (!link.href) {
return (
<span
key={link.label}
className="font-mono uppercase text-xs text-scale-900"
>
{link.label}
</span>
)
} else {
return (
<Link href={link.href} passHref key={link.label}>
<a>
<li
className={[
'group flex items-center gap-3',
'text-base transition-all duration-150 text-scale-1200 hover:text-brand-900 hover:cursor-pointer ',
].join(' ')}
>
<Image
alt={link.label}
src={`${router.basePath}${
isDarkMode ? link.icon : `${link.icon}-light`
}${!link.icon.includes('png') ? '.svg' : ''}`}
width={17}
height={17}
className="w-4 h-4 group-hover:scale-110 ease-out transition-all"
/>
{link.label}
</li>
</a>
</Link>
)
}
})}
</div>
</div>
</>
)
})}
</ul>
</div>
)
}
export default NavigationMenuHome
@@ -0,0 +1,37 @@
import { createContext, useContext } from 'react'
interface ContextProps {
activeRefItem: string
setActiveRefItem: (x: string) => void
}
interface Provider extends ContextProps {
children?: React.ReactNode
}
// Make sure the shape of the default value passed to
// createContext matches the shape that the consumers expect!
const NavMenuContext = createContext<ContextProps>({
activeRefItem: undefined,
setActiveRefItem: undefined,
})
export const NavigationMenuContextProvider = (props: Provider) => {
const { activeRefItem, setActiveRefItem } = props
const value = {
activeRefItem,
setActiveRefItem,
}
return <NavMenuContext.Provider value={value}>{props.children}</NavMenuContext.Provider>
}
// context helper to avoid using a consumer component
export const useNavigationMenuContext = () => {
const context = useContext(NavMenuContext)
if (context === undefined) {
throw new Error(`useFormContextOnChange must be used within a FormContextProvider.`)
}
return context
}
@@ -0,0 +1,899 @@
// import SupabaseJsV1Nav from 'data/nav/supabase-js/v1'
// import SupabaseJsV2Nav from 'data/nav/supabase-js/v2'
// import SupabaseDartV0Nav from 'data/nav/supabase-dart/v0'
// import SupabaseDartV1Nav from 'data/nav/supabase-dart/v1'
// import SupabaseCLINav from 'data/nav/supabase-cli'
// import SupabaseAPINav from 'data/nav/supabase-api'
// import AuthServerNav from 'data/nav/auth-server'
// import StorageServerNav from 'data/nav/storage-server'
import { NavMenu, References } from '../Navigation.types'
export const REFERENCES: References = {
javascript: {
name: 'supabase-js',
library: 'supabase-js',
versions: ['v2', 'v1'],
icon: '/docs/img/libraries/javascript-icon.svg',
},
dart: {
name: 'Flutter',
library: 'supabase-dart',
versions: ['v1', 'v0'],
icon: '/docs/img/libraries/flutter-icon.svg',
},
cli: {
name: 'CLI',
library: undefined,
versions: [],
icon: '/docs/img/icons/cli-icon.svg',
},
api: {
name: 'API',
library: undefined,
versions: [],
icon: '/docs/img/icons/api-icon.svg',
},
}
export const gettingstarted = {
icon: 'getting-started',
title: 'Getting Started',
items: [
{ name: 'Features', url: '/guides/getting-started/features', items: [] },
{ name: 'Architecture', url: '/guides/getting-started/architecture', items: [] },
// {
// name: 'Quickstarts',
// items: [
// { name: 'NextJS', url: '/guides/getting-started/quickstarts/nextjs', items: [] },
// { name: 'ReactJS', url: '/guides/getting-started/quickstarts/reactjs', items: [] },
// ],
// },
{
name: 'Web app tutorials',
items: [
{
name: 'Next.js',
url: '/guides/getting-started/tutorials/with-nextjs',
items: [],
},
{
name: 'React',
url: '/guides/getting-started/tutorials/with-react',
items: [],
},
{
name: 'Vue 3',
url: '/guides/getting-started/tutorials/with-vue-3',
items: [],
},
{
name: 'Nuxt 3',
url: '/guides/getting-started/tutorials/with-nuxt-3',
items: [],
},
{
name: 'Angular',
url: '/guides/getting-started/tutorials/with-angular',
items: [],
},
{
name: 'RedwoodJS',
url: '/guides/getting-started/tutorials/with-redwoodjs',
items: [],
},
{
name: 'SolidJS',
url: '/guides/getting-started/tutorials/with-solidjs',
items: [],
},
{
name: 'Svelte',
url: '/guides/getting-started/tutorials/with-svelte',
items: [],
},
{
name: 'SvelteKit',
url: '/guides/getting-started/tutorials/with-sveltekit',
items: [],
},
],
},
{
name: 'Mobile tutorials',
items: [
{
name: 'Flutter',
url: '/guides/getting-started/tutorials/with-flutter',
items: [],
},
{
name: 'Expo',
url: '/guides/getting-started/tutorials/with-expo',
items: [],
},
{
name: 'Ionic React',
url: '/guides/getting-started/tutorials/with-ionic-react',
items: [],
},
{
name: 'Ionic Vue',
url: '/guides/getting-started/tutorials/with-ionic-vue',
items: [],
},
{
name: 'Ionic Angular',
url: '/guides/getting-started/tutorials/with-ionic-angular',
items: [],
},
],
},
],
}
export const cli = {
label: 'CLI',
items: [
{ name: 'Overview', url: '/guides/cli', items: [] },
{ name: 'Local Development', url: '/guides/cli/local-development', items: [] },
{ name: 'Managing Environments', url: '/guides/cli/managing-environments', items: [] },
],
}
export const SocialLoginItems = [
{
name: 'Google',
icon: '/docs/img/icons/google-icon',
url: '/guides/auth/social-login/auth-google',
items: [],
},
{
name: 'Facebook',
icon: '/docs/img/icons/facebook-icon',
url: '/guides/auth/social-login/auth-facebook',
items: [],
},
{
name: 'Apple',
icon: '/docs/img/icons/apple-icon',
url: '/guides/auth/social-login/auth-apple',
items: [],
},
{
name: 'Azure',
icon: '/docs/img/icons/microsoft-icon',
url: '/guides/auth/social-login/auth-azure',
items: [],
},
{
name: 'Twitter',
icon: '/docs/img/icons/twitter-icon',
url: '/guides/auth/social-login/auth-twitter',
items: [],
},
{
name: 'GitHub',
icon: '/docs/img/icons/github-icon',
url: '/guides/auth/social-login/auth-github',
items: [],
isDarkMode: true,
},
{
name: 'Gitlab',
icon: '/docs/img/icons/gitlab-icon',
url: '/guides/auth/social-login/auth-gitlab',
items: [],
},
{
name: 'Bitbucket',
icon: '/docs/img/icons/bitbucket-icon',
url: '/guides/auth/social-login/auth-bitbucket',
items: [],
},
{
name: 'Discord',
icon: '/docs/img/icons/discord-icon',
url: '/guides/auth/social-login/auth-discord',
items: [],
},
{
name: 'Keycloak',
icon: '/docs/img/icons/keycloak-icon',
url: '/guides/auth/social-login/auth-keycloak',
items: [],
},
{
name: 'LinkedIn',
icon: '/docs/img/icons/linkedin-icon',
url: '/guides/auth/social-login/auth-linkedin',
items: [],
},
{
name: 'Notion',
icon: '/docs/img/icons/notion-icon',
url: '/guides/auth/social-login/auth-notion',
items: [],
},
{
name: 'Slack',
icon: '/docs/img/icons/slack-icon',
url: '/guides/auth/social-login/auth-slack',
items: [],
},
{
name: 'Spotify',
icon: '/docs/img/icons/spotify-icon',
url: '/guides/auth/social-login/auth-spotify',
items: [],
},
{
name: 'Twitch',
icon: '/docs/img/icons/twitch-icon',
url: '/guides/auth/social-login/auth-twitch',
items: [],
},
{
name: 'WorkOS',
icon: '/docs/img/icons/workos-icon',
url: '/guides/auth/social-login/auth-workos',
items: [],
},
{
name: 'Zoom',
icon: '/docs/img/icons/zoom-icon',
url: '/guides/auth/social-login/auth-zoom',
items: [],
},
]
export const PhoneLoginsItems = [
{
name: 'MessageBird SMS Login',
icon: '/docs/img/icons/messagebird-icon',
linkDescription: 'Communication between businesses and their customers — across any channel.',
url: '/guides/auth/phone-login/messagebird',
items: [],
},
{
name: 'Twilio SMS Login',
icon: '/docs/img/icons/twilio-icon',
url: '/guides/auth/phone-login/twilio',
linkDescription: 'Customer engagement platform used by hundreds of thousands of businesses.',
items: [],
},
{
name: 'Vonage SMS Login',
icon: '/docs/img/icons/vonage-icon',
url: '/guides/auth/phone-login/vonage',
linkDescription:
'Vonage is a communication platform as a service (CPaaS) provider for consumers and businesses.',
items: [],
isDarkMode: true,
},
]
export const auth = {
icon: 'auth',
label: 'Auth',
items: [
{
name: 'Overview',
url: '/guides/auth/overview',
},
{
name: 'Authentication',
url: undefined,
items: [
{ name: 'Email Login', url: '/guides/auth/auth-email', items: [] },
{ name: 'Magic Link Login', url: '/guides/auth/auth-magic-link', items: [] },
{
name: 'Phone Login',
url: '/guides/auth/phone-login',
items: [...PhoneLoginsItems],
},
{
name: 'Social Login',
url: '/guides/auth/social-login',
items: [...SocialLoginItems],
},
{ name: 'Email Templates', url: '/guides/auth/auth-email-templates', items: [] },
],
},
{
name: 'Authorization',
url: undefined,
items: [
{ name: 'Enable Captcha Protection', url: '/guides/auth/auth-captcha', items: [] },
{ name: 'Managing User Data', url: '/guides/auth/managing-user-data', items: [] },
{ name: 'Multi-Factor Authentication', url: '/guides/auth/auth-mfa', items: [] },
{ name: 'Row Level Security', url: '/guides/auth/row-level-security', items: [] },
{ name: 'Server-side Rendering', url: '/guides/auth/server-side-rendering', items: [] },
],
},
{
name: 'Auth Helpers',
url: undefined,
items: [
{ name: 'Overview', url: '/guides/auth/auth-helpers', items: [] },
{ name: 'Auth UI', url: '/guides/auth/auth-helpers/auth-ui', items: [] },
{ name: 'Next.js', url: '/guides/auth/auth-helpers/nextjs', items: [] },
{ name: 'Remix', url: '/guides/auth/auth-helpers/remix', items: [] },
{ name: 'SvelteKit', url: '/guides/auth/auth-helpers/sveltekit', items: [] },
],
},
{
name: 'Deep Dive',
url: undefined,
items: [
{
name: 'Part One: JWTs',
url: '/learn/auth-deep-dive/auth-deep-dive-jwts',
items: [],
},
{
name: 'Part Two: Row Level Security',
url: '/learn/auth-deep-dive/auth-row-level-security',
items: [],
},
{ name: 'Part Three: Policies', url: '/learn/auth-deep-dive/auth-policies', items: [] },
{ name: 'Part Four: GoTrue', url: '/learn/auth-deep-dive/auth-gotrue', items: [] },
{
name: 'Part Five: Google OAuth',
url: '/learn/auth-deep-dive/auth-google-oauth',
items: [],
},
],
},
],
}
export const database = {
icon: 'database',
label: 'Database',
url: '/guides/database',
items: [
{ name: 'Database Connections', url: '/guides/database/connecting-to-postgres', items: [] },
{ name: 'Tables and Data', url: '/guides/database/tables', items: [] },
{ name: 'Database Functions', url: '/guides/database/functions', items: [] },
{ name: 'Database Webhooks', url: '/guides/database/webhooks', items: [] },
{ name: 'Full Text Search', url: '/guides/database/full-text-search', items: [] },
{ name: 'Database Testing', url: '/guides/database/testing', items: [] },
{
name: 'Serverless APIs',
url: undefined,
items: [
{ name: 'Overview', url: '/guides/api', items: [] },
{ name: 'Generating Types', url: '/guides/api/generating-types', items: [] },
],
},
{
name: 'Extensions',
url: undefined,
items: [
{ name: 'Overview', url: '/guides/database/extensions', items: [] },
{
name: 'plv8: Javascript Language',
url: '/guides/database/extensions/plv8',
items: [],
},
{ name: 'http: RESTful Client', url: '/guides/database/extensions/http', items: [] },
{
name: 'pg_cron: Job Scheduling',
url: '/guides/database/extensions/pgcron',
items: [],
},
{
name: 'pg_net: Async Networking',
url: '/guides/database/extensions/pgnet',
items: [],
},
{ name: 'pgTAP: Unit Testing', url: '/guides/database/extensions/pgtap', items: [] },
{
name: 'uuid-ossp: Unique Identifiers',
url: '/guides/database/extensions/uuid-ossp',
items: [],
},
],
},
{
name: 'Configuration',
url: undefined,
items: [
{ name: 'Timeouts', url: '/guides/database/timeouts', items: [] },
{ name: 'Replication', url: '/guides/database/replication', items: [] },
{ name: 'Passwords', url: '/guides/database/managing-passwords', items: [] },
{ name: 'Timezones', url: '/guides/database/managing-timezones', items: [] },
],
},
],
}
export const functions = {
icon: 'functions',
label: 'Edge Functions',
url: '/guides/functions',
items: [
{ name: 'Auth', url: '/guides/functions/auth', items: [] },
{ name: 'CI/CD Workflow', url: '/guides/functions/cicd-workflow', items: [] },
{ name: 'Best Practices', url: '/guides/functions/best-practices', items: [] },
{ name: 'Quickstart', url: '/guides/functions/quickstart', items: [] },
{
name: 'Examples',
url: '/guides/functions/examples',
items: [{ name: 'OG Image', url: '/guides/functions/examples/og-image', items: [] }],
},
],
}
export const realtime = {
icon: 'realtime',
label: 'Realtime',
url: '/guides/realtime',
items: [
{ name: 'Overview', url: '/guides/realtime', items: [] },
{ name: 'Quickstart', url: '/guides/realtime/quickstart', items: [] },
{
name: 'Channels',
url: undefined,
items: [
{ name: 'Postgres Changes', url: '/guides/realtime/postgres-changes', items: [] },
{ name: 'Rate Limits', url: '/guides/realtime/rate-limits', items: [] },
],
},
],
}
export const storage = {
icon: 'storage',
label: 'Storage',
url: '/guides/storage',
items: [
{ name: 'Overview', url: '/guides/storage', items: [] },
{ name: 'Quickstart', url: '/guides/storage/quickstart', items: [] },
{ name: 'Access Control', url: '/guides/storage/access-control', items: [] },
{ name: 'CDN', url: '/guides/storage/cdn', items: [] },
{ name: 'Image Transformations', url: '/guides/storage/image-transformations', items: [] },
],
}
export const platform = {
icon: 'platform',
label: 'Platform',
url: '/guides/platform',
items: [
{
name: 'Add-ons',
url: undefined,
items: [
{ name: 'Compute Add-ons', url: '/guides/platform/compute-add-ons', items: [] },
{ name: 'Custom Domains', url: '/guides/platform/custom-domains', items: [] },
{ name: 'Database Backups', url: '/guides/platform/backups', items: [] },
],
},
{
name: 'Platform Management',
url: undefined,
items: [
{ name: 'Access Control', url: '/guides/platform/access-control', items: [] },
{ name: 'Database Usage', url: '/guides/platform/database-usage', items: [] },
{ name: 'Logging', url: '/guides/platform/logs', items: [] },
{ name: 'Metrics', url: '/guides/platform/metrics', items: [] },
{
name: 'Migrating and Upgrading',
url: '/guides/platform/migrating-and-upgrading-projects',
items: [],
},
{ name: 'Network Restrictions', url: '/guides/platform/network-restrictions', items: [] },
{ name: 'Performance Tuning', url: '/guides/platform/performance', items: [] },
{ name: 'Permissions', url: '/guides/platform/permissions', items: [] },
],
},
{
name: 'Single sign-on',
url: undefined,
items: [
{
name: 'Enable SSO for your organization',
url: '/guides/platform/sso',
items: [],
},
{ name: 'SSO with Azure AD', url: '/guides/platform/sso/azure', items: [] },
{
name: 'SSO with Google Workspace',
url: '/guides/platform/sso/gsuite',
items: [],
},
{ name: 'SSO with Okta', url: '/guides/platform/sso/okta', items: [] },
],
},
{
name: 'Go-live Checklist',
url: undefined,
items: [{ name: 'Production Readiness', url: '/guides/platform/going-into-prod', items: [] }],
},
],
}
export const resources = {
icon: 'resources',
label: 'Resources',
url: '/guides/resources',
items: [
// removing until the examples page is reworked
// { name: 'Examples', url: '/guides/resources/examples', items: [] },
{ name: 'Glossary', url: '/guides/resources/glossary', items: [] },
{
name: 'Migrate to Supabase',
url: '/guides/resources/migrating-to-supabase',
items: [
{
name: 'Firebase Auth',
url: '/guides/resources/migrating-to-supabase/firebase-auth',
items: [],
},
{
name: 'Firestore Data',
url: '/guides/resources/migrating-to-supabase/firestore-data',
items: [],
},
{
name: 'Firebase Storage',
url: '/guides/resources/migrating-to-supabase/firebase-storage',
items: [],
},
{
name: 'Heroku',
url: '/guides/resources/migrating-to-supabase/heroku',
items: [],
},
],
},
{
name: 'Supabase CLI',
items: [
{
name: 'Overview',
url: '/guides/resources/supabase-cli',
items: [],
},
{
name: 'Local Development',
url: '/guides/resources/supabase-cli/local-development',
items: [],
},
{
name: 'Managing Environments',
url: '/guides/resources/supabase-cli/managing-environments',
items: [],
},
],
},
{
name: 'Self-Hosting',
items: [
{ name: 'Overview', url: '/guides/resources/self-hosting', items: [] },
{ name: 'Docker', url: '/guides/resources/self-hosting/docker', items: [] },
],
},
{
name: 'Self-Hosting Reference',
items: [
{
name: 'Auth Server',
url: '/reference/self-hosting-auth/introduction',
icon: '/img/icons/menu/reference-auth',
items: [],
},
{
name: 'Storage Server',
url: '/reference/self-hosting-storage/introduction',
icon: '/img/icons/menu/reference-storage',
items: [],
},
{
name: 'Realtime Server',
url: '/reference/self-hosting-realtime/introduction',
icon: '/img/icons/menu/reference-realtime',
items: [],
},
],
},
],
}
export const selfHosting = {
label: 'Self-Hosting',
url: '/guides/self-hosting',
items: [
{ name: 'Overview', url: '/guides/hosting/overview', items: [] },
{ name: 'Docker', url: '/guides/hosting/docker', items: [] },
],
}
export const migrate = {
label: 'Migrate to Supabase',
url: '/guides/migrate',
items: [
{ name: 'Firebase Auth', url: '/guides/migrations/firebase-auth', items: [] },
{ name: 'Firestore Data', url: '/guides/migrations/firestore-data', items: [] },
{ name: 'Firebase Storage', url: '/guides/migrations/firebase-storage', items: [] },
{ name: 'Heroku', url: '/guides/migrations/heroku', items: [] },
],
}
export const integrations = {
icon: 'integrations',
label: 'Integrations',
url: '/guides/integrations',
items: [
{ name: 'Overview', url: '/guides/integrations/integrations', items: [] },
{
name: 'Auth',
url: undefined,
items: [
{
name: 'Auth0',
url: '/guides/integrations/auth0',
items: [],
},
{ name: 'Authsignal', url: '/guides/integrations/authsignal', items: [] },
{ name: 'Clerk', url: '/guides/integrations/clerk', items: [] },
{ name: 'keyri', url: '/guides/integrations/keyri', items: [] },
{ name: 'Stytch', url: '/guides/integrations/stytch', items: [] },
{ name: 'SuperTokens', url: '/guides/integrations/supertokens', items: [] },
],
},
{
name: 'Caching / Offline-first',
url: undefined,
items: [{ name: 'Polyscale', url: '/guides/integrations/polyscale', items: [] }],
},
{
name: 'Developer Tools',
url: undefined,
items: [
{ name: 'Estuary', url: '/guides/integrations/estuary', items: [] },
{ name: 'pgMustard', url: '/guides/integrations/pgmustard', items: [] },
{ name: 'Prisma', url: '/guides/integrations/prisma', items: [] },
{ name: 'Sequin', url: '/guides/integrations/sequin', items: [] },
{ name: 'Snaplet', url: '/guides/integrations/snaplet', items: [] },
{ name: 'Vercel', url: '/guides/integrations/vercel', items: [] },
{ name: 'WeWeb', url: '/guides/integrations/weweb', items: [] },
{ name: 'Zuplo', url: '/guides/integrations/zuplo', items: [] },
],
},
{
name: 'Low-code',
url: undefined,
items: [
{ name: 'Appsmith', url: '/guides/integrations/appsmith', items: [] },
{ name: 'Dashibase', url: '/guides/integrations/dashibase', items: [] },
{ name: 'DhiWise', url: '/guides/integrations/dhiwise', items: [] },
{ name: 'Directus', url: '/guides/integrations/directus', items: [] },
{ name: 'Draftbit', url: '/guides/integrations/draftbit', items: [] },
{ name: 'Plasmic', url: '/guides/integrations/plasmic', items: [] },
],
},
{
name: 'Messaging',
url: undefined,
items: [{ name: 'OneSignal', url: '/guides/integrations/onesignal', items: [] }],
},
],
}
export const reference = {
title: 'API Reference',
icon: 'reference',
items: [
{
name: 'Client libraries',
items: [
{
name: 'supabase-js',
url: '/reference/javascript/start',
level: 'reference_javascript',
items: [],
icon: 'docs/img/icons/javascript.svg',
},
{
name: 'supabase-dart',
url: '/reference/dart/start',
level: 'reference_dart',
items: [],
icon: 'docs/img/icons/javascript.svg',
},
{
name: 'supbase-python',
url: '/reference/python/start',
level: 'reference_python',
items: [],
icon: 'docs/img/icons/javascript.svg',
},
],
},
{
name: 'Other tools',
items: [
{
name: 'Supabase CLI',
url: '/reference/cli/start',
items: [],
icon: 'docs/img/icons/cli-icon.svg',
},
{
name: 'Management API',
url: '/reference/javascript',
items: [],
icon: 'docs/img/icons/javascript-icon.svg',
},
],
},
],
}
export const reference_javascript_v1 = {
icon: 'reference-javascript',
title: 'javascript',
url: '/guides/reference/javascript',
parent: '/reference',
}
export const reference_javascript_v2 = {
icon: 'reference-javascript',
title: 'javascript',
url: '/guides/reference/javascript',
parent: '/reference',
}
export const reference_dart_v0 = {
icon: 'reference-dart',
title: 'dart',
url: '/guides/reference/dart',
parent: '/reference',
}
export const reference_dart_v1 = {
icon: 'reference-dart',
title: 'dart',
url: '/guides/reference/dart',
parent: '/reference',
}
export const reference_cli = {
icon: 'reference-cli',
title: 'Supabase CLI',
url: '/guides/reference/cli',
parent: '/',
}
export const reference_api = {
icon: 'reference-api',
title: 'Management API',
url: '/guides/reference/api',
parent: '/reference',
}
export const reference_self_hosting_auth = {
icon: 'reference-auth',
title: 'Self-Hosting Auth',
url: '/guides/reference/self-hosting/auth',
parent: '/reference',
}
export const reference_self_hosting_storage = {
icon: 'reference-storage',
title: 'Self-Hosting Storage',
url: '/guides/reference/self-hosting/storage',
parent: '/reference',
}
export const reference_self_hosting_realtime = {
icon: 'reference-auth',
title: 'Self-Hosting Realtime',
url: '/guides/reference/self-hosting/realtime',
parent: '/reference',
}
// export const reference: [
// {
// label: 'Official'
// items: [
// { name: 'Reference Documentation'; url: '/reference'; items: [] },
// { name: 'Supabase JavaScript Library'; url: '/reference/javascript'; items: [] },
// { name: 'Supabase Flutter Library'; url: '/reference/dart'; items: [] },
// { name: 'Supabase CLI'; url: '/reference/cli'; items: [] },
// { name: 'Management API'; url: '/reference/api'; items: [] }
// ]
// },
// {
// label: 'Self-hosting'
// items: [
// { name: 'Auth Server'; url: '/reference/auth'; items: [] },
// { name: 'Storage Server'; url: '/reference/storage'; items: [] }
// ]
// }
// {
// label: 'Clients',
// items: [
// { name: 'Auth Server', url: '/reference/auth', items: [] },
// { name: 'Storage Server', url: '/reference/storage', items: [] },
// ],
// },
// 'reference/javascript': SupabaseJsV2Nav,
// 'reference/javascript/v1': SupabaseJsV1Nav,
// 'reference/dart': SupabaseDartV1Nav,
// 'reference/dart/v0': SupabaseDartV0Nav,
// 'reference/cli': SupabaseCLINav,
// 'reference/api': SupabaseAPINav,
// 'reference/auth': AuthServerNav,
// 'reference/storage': StorageServerNav,
// ]
export const references = [
{
label: 'Client libraries',
items: [
{
label: 'supabase-js',
versions: ['v2', 'v1'],
description: 'something about the reference',
icon: '/docs/img/icons/javascript-icon.svg',
url: '/reference/javascript/start',
},
{
label: 'supabase-py',
description: 'something about the reference',
icon: '/docs/img/icons/python-icon.svg',
url: '/reference/python/start',
},
{
label: 'supabase-dart',
versions: ['v1', 'v0'],
description: 'something about the reference',
icon: '/docs/img/icons/dart-icon.svg',
url: '/reference/dart/start',
},
],
},
{
label: 'Platform Tools',
items: [
{
label: 'CLI',
description: 'something about the reference',
icon: '/docs/img/icons/cli-icon.svg',
url: '/reference/cli/start',
},
{
label: 'Management API',
description: 'something about the reference',
icon: '/docs/img/icons/api-icon.svg',
url: '/reference/management-api/start',
},
],
},
{
label: 'Self-Hosting',
items: [
{
label: 'Auth server',
description: 'something about the reference',
icon: '/docs/img/icons/menu/auth.svg',
url: '/reference/auth/start',
},
{
label: 'Storage server',
description: 'something about the reference',
icon: '/docs/img/icons/menu/storage.svg',
url: '/reference/storage/start',
},
{
label: 'Realtime server',
description: 'something about the reference',
icon: '/docs/img/icons/menu/realtime.svg',
url: '/reference/realtime/start',
},
],
},
]
@@ -0,0 +1,235 @@
import { useRouter } from 'next/router'
import { memo, useEffect } from 'react'
import { menuState } from '~/hooks/useMenuState'
import NavigationMenuGuideList from './NavigationMenuGuideList'
import NavigationMenuRefList from './NavigationMenuRefList'
// @ts-expect-error
import spec_js_v2 from '~/../../spec/supabase_js_v2.yml' assert { type: 'yml' }
// @ts-expect-error
import spec_js_v1 from '~/../../spec/supabase_js_v1.yml' assert { type: 'yml' }
// @ts-expect-error
import spec_dart_v1 from '~/../../spec/supabase_dart_v1.yml' assert { type: 'yml' }
// @ts-expect-error
import spec_dart_v0 from '~/../../spec/supabase_dart_v0.yml' assert { type: 'yml' }
// import { gen_v3 } from '~/lib/refGenerator/helpers'
import apiCommonSections from '~/../../spec/common-api-sections.json'
import cliCommonSections from '~/../../spec/common-cli-sections.json'
import libCommonSections from '~/../../spec/common-client-libs-sections.json'
import authServerCommonSections from '~/../../spec/common-self-hosting-auth-sections.json'
import realtimeServerCommonSections from '~/../../spec/common-self-hosting-realtime-sections.json'
import storageServerCommonSections from '~/../../spec/common-self-hosting-storage-sections.json'
import { flattenSections } from '~/lib/helpers'
import NavigationMenuHome from './HomeMenu'
// Filter libCommonSections for just the relevant sections in the current library
function generateAllowedClientLibKeys(sections, spec) {
// Filter parent sections first
const specIds = spec.functions.map((func) => {
return func.id
})
const newShape = flattenSections(sections).filter((section) => {
if (specIds.includes(section.id)) {
return section
}
})
const final = newShape.map((func) => {
return func.id
})
return final
}
export type RefIdOptions =
| 'reference_javascript_v1'
| 'reference_javascript_v2'
| 'reference_dart_v0'
| 'reference_dart_v1'
| 'reference_cli'
| 'reference_api'
| 'reference_self_hosting_auth'
| 'reference_self_hosting_storage'
| 'reference_self_hosting_realtime'
export type RefKeyOptions =
| 'javascript'
| 'dart'
| 'cli'
| 'api'
| 'self-hosting-auth'
| 'self-hosting-storage'
| 'self-hosting-realtime'
const SideNav = () => {
const router = useRouter()
let version = ''
if (router.asPath.includes('v1')) {
version = '_v1'
}
if (router.asPath.includes('v0')) {
version = '_v0'
}
function handleRouteChange(url: string) {
switch (url) {
case `/docs`:
menuState.setMenuLevelId('home')
break
case url.includes(`/docs/guides/getting-started`) && url:
menuState.setMenuLevelId('gettingstarted')
break
case url.includes(`/docs/guides/database`) && url:
menuState.setMenuLevelId('database')
break
case url.includes(`/docs/guides/auth`) && url:
menuState.setMenuLevelId('auth')
break
case url.includes(`/docs/guides/functions`) && url:
menuState.setMenuLevelId('functions')
break
case url.includes(`/docs/guides/realtime`) && url:
menuState.setMenuLevelId('realtime')
break
case url.includes(`/docs/guides/storage`) && url:
menuState.setMenuLevelId('storage')
break
case url.includes(`/docs/guides/platform`) && url:
menuState.setMenuLevelId('platform')
break
case url.includes(`/docs/guides/resources`) && url:
menuState.setMenuLevelId('resources')
break
case url.includes(`/docs/guides/integrations`) && url:
menuState.setMenuLevelId('integrations')
break
// JS v1
case url.includes(`/docs/reference/javascript/v1`) && url:
menuState.setMenuLevelId('reference_javascript_v1')
break
// JS v2 (latest)
case url.includes(`/docs/reference/javascript`) && url:
menuState.setMenuLevelId('reference_javascript_v2')
break
// dart v0
case url.includes(`/docs/reference/dart/v0`) && url:
menuState.setMenuLevelId('reference_dart_v0')
break
// dart v1 (latest)
case url.includes(`/docs/reference/dart`) && url:
menuState.setMenuLevelId('reference_dart_v1')
break
case url.includes(`/docs/reference/cli`) && url:
menuState.setMenuLevelId('reference_cli')
break
case url.includes(`/docs/reference/api`) && url:
menuState.setMenuLevelId('reference_api')
break
case url.includes(`/docs/reference/self-hosting-auth`) && url:
menuState.setMenuLevelId('reference_self_hosting_auth')
break
case url.includes(`/docs/reference/self-hosting-storage`) && url:
menuState.setMenuLevelId('reference_self_hosting_storage')
break
case url.includes(`/docs/reference/self-hosting-realtime`) && url:
menuState.setMenuLevelId('reference_self_hosting_realtime')
break
default:
break
}
}
useEffect(() => {
handleRouteChange(router.basePath + router.asPath)
// Listen for page changes after a navigation or when the query changes
router.events.on('routeChangeComplete', handleRouteChange)
return () => {
router.events.off('routeChangeComplete', handleRouteChange)
}
}, [router.events])
return (
<div className="flex relative">
{/* // main menu */}
<NavigationMenuHome />
<NavigationMenuGuideList id={'gettingstarted'} />
<NavigationMenuGuideList id={'database'} />
<NavigationMenuGuideList id={'auth'} />
<NavigationMenuGuideList id={'functions'} />
<NavigationMenuGuideList id={'realtime'} />
<NavigationMenuGuideList id={'storage'} />
<NavigationMenuGuideList id={'platform'} />
<NavigationMenuGuideList id={'resources'} />
<NavigationMenuGuideList id={'integrations'} />
<NavigationMenuGuideList id={'reference'} />
{/* // Client Libs */}
<NavigationMenuRefList
key={'reference-js-menu'}
id={'reference_javascript_v1'}
commonSections={libCommonSections}
lib="javascript"
allowedClientKeys={generateAllowedClientLibKeys(libCommonSections, spec_js_v1)}
/>
<NavigationMenuRefList
key={'reference-js-menu'}
id={'reference_javascript_v2'}
commonSections={libCommonSections}
lib="javascript"
allowedClientKeys={generateAllowedClientLibKeys(libCommonSections, spec_js_v2)}
/>
<NavigationMenuRefList
key={'reference-dart-menu'}
id={'reference_dart_v0'}
commonSections={libCommonSections}
lib="dart"
allowedClientKeys={generateAllowedClientLibKeys(libCommonSections, spec_dart_v0)}
/>
<NavigationMenuRefList
key={'reference-dart-menu'}
id={'reference_dart_v1'}
commonSections={libCommonSections}
lib="dart"
allowedClientKeys={generateAllowedClientLibKeys(libCommonSections, spec_dart_v1)}
/>
{/* // Tools */}
<NavigationMenuRefList
key={'reference-cli-menu'}
id={'reference_cli'}
commonSections={cliCommonSections}
lib="cli"
/>
<NavigationMenuRefList
key={'reference-api-menu'}
id={'reference_api'}
commonSections={apiCommonSections}
lib="api"
/>
{/* // Self Hosting Server */}
<NavigationMenuRefList
key={'reference-self-hosting-auth-menu'}
id={'reference_self_hosting_auth'}
commonSections={authServerCommonSections}
lib="self-hosting-auth"
/>
<NavigationMenuRefList
key={'reference-self-hosting-storage-menu'}
id={'reference_self_hosting_storage'}
commonSections={storageServerCommonSections}
lib="self-hosting-storage"
/>
<NavigationMenuRefList
key={'reference-self-hosting-realtime-menu'}
id={'reference_self_hosting_realtime'}
commonSections={realtimeServerCommonSections}
lib="self-hosting-auth"
/>
</div>
)
}
export default memo(SideNav)
@@ -0,0 +1,170 @@
import Link from 'next/link'
import { useRouter } from 'next/router'
import { IconChevronLeft } from 'ui'
import * as NavItems from './NavigationMenu.constants'
// @ts-expect-error
import clientLibsCommon from '~/../../spec/common-cli.yml' assert { type: 'yml' }
const NavigationMenuCliList = ({ currentLevel, setLevel, id }) => {
const router = useRouter()
const menu = NavItems[id]
const FunctionLink = ({
title,
id,
icon,
}: {
title: string
name: string
id: string
icon?: string
}) => {
return (
<li key={id} className="function-link-item text-scale-1000">
<Link href={`#${id}`} passHref>
<a className="cursor-pointer transition text-scale-1000 text-sm hover:text-brand-900 flex gap-3">
{icon && <img className="w-3" src={`${router.basePath}${icon}`} />}
{title}
</a>
</Link>
</li>
)
}
const SideMenuTitle = ({ title }: { title: string }) => {
return (
<span
className="
font-mono text-xs uppercase
text-scale-1200 font-medium
tracking-wider
mb-3
"
>
{title}
</span>
)
}
const Divider = () => {
return <div className="h-px w-full bg-green-500 my-3"></div>
}
const MenuSections = [
{
key: 'general',
label: 'General',
},
{
key: 'secrets',
label: 'Secrets',
},
{
key: 'projects',
label: 'Projects',
},
{
key: 'organizations',
label: 'Organizations',
},
{
key: 'migration',
label: 'Migration',
},
{
key: 'database',
label: 'Database',
},
{
key: 'completion',
label: 'Completion',
},
]
return (
<div
className={[
'transition-all ml-8 duration-150 ease-out',
// enabled
currentLevel === id && 'opacity-100 ml-0 delay-150 h-auto',
// move menu back to margin-left
currentLevel === 'home' && 'ml-12',
// disabled
currentLevel !== 'home' && currentLevel !== id ? '-ml-8' : '',
currentLevel !== id ? 'opacity-0 invisible absolute h-0 overflow-hidden' : '',
].join(' ')}
>
<div className={'w-full flex flex-col gap-0 sticky top-8'}>
<Link href={`${menu.parent ?? '/'}`} passHref>
<a
className={[
'flex items-center gap-1 text-xs group mb-3',
'text-base transition-all duration-200 text-brand-900 hover:text-brand-1200 hover:cursor-pointer ',
].join(' ')}
>
<div className="relative w-2">
<div className="transition-all ease-out ml-0 group-hover:-ml-1">
<IconChevronLeft size={10} strokeWidth={3} />
</div>
</div>
<span>Back to menu</span>
</a>
</Link>
<div className="flex items-center gap-3 my-3">
<img
src={`${router.basePath}` + menu.icon ?? `/img/icons/menu/${id}.svg`}
className="w-5 rounded"
/>
<h2 className={['text-scale-1200 ', !menu.title && 'capitalize'].join(' ')}>
{menu.title ?? currentLevel}
</h2>
</div>
<ul className="function-link-list">
{MenuSections.map((section) => {
return (
<>
<Divider />
<SideMenuTitle title={section.label} />
{clientLibsCommon.commands
.filter((x) => x.product === section.key)
.map((x, index) => {
return <FunctionLink {...x} />
})}
</>
)
})}
<Divider />
</ul>
{menu.extras && (
<>
<Divider />{' '}
<span className="font-mono text-xs uppercase text-scale-1200 font-medium tracking-wider mb-2">
Resources
</span>
</>
)}
{menu.extras?.map((x) => {
return (
<div key={x.name}>
<li>
<Link href={`${x.href}`} passHref>
<a className="cursor-pointer transition text-scale-1100 text-sm hover:text-brand-900 flex gap-3 my-1">
{x.icon && <img className="w-4" src={`${router.basePath}${x.icon}`} />}
{x.name}
</a>
</Link>
</li>
</div>
)
})}
</div>
</div>
)
}
export default NavigationMenuCliList
@@ -0,0 +1,231 @@
import * as Accordion from '@radix-ui/react-accordion'
import { useTheme } from 'common/Providers'
import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import React from 'react'
import { IconChevronLeft } from '~/../../packages/ui'
import { useMenuLevelId } from '~/hooks/useMenuState'
import * as NavItems from './NavigationMenu.constants'
interface Props {
id: string
setMenuLevelId?: any
}
const NavigationMenuGuideList: React.FC<Props> = ({ id, setMenuLevelId }) => {
const router = useRouter()
const { isDarkMode } = useTheme()
const menu = NavItems[id]
//console.log(id, 'url is:', menu.url)
// get url
const url = router.asPath
// remove the end of the url if a deep link
const firstLevelRoute = url?.split('/')?.slice(0, 4)?.join('/')
const level = useMenuLevelId()
return (
<Accordion.Root
collapsible
key={id}
type="single"
value={firstLevelRoute}
className={[
'transition-all ml-8 duration-150 ease-out',
// enabled
level === id && 'opacity-100 ml-0 delay-150',
level === 'home' && 'ml-12',
// disabled
level !== 'home' && level !== id ? '-ml-8' : '',
level !== id ? 'opacity-0 invisible absolute' : '',
].join(' ')}
>
<ul className={['relative w-full flex flex-col gap-0'].join(' ')}>
<Link href={`${menu.parent ?? '/'}`} passHref>
<a
className={[
'flex items-center gap-1 text-xs group mb-3',
'text-base transition-all duration-200 text-brand-900 hover:text-brand-1200 hover:cursor-pointer ',
].join(' ')}
>
<div className="relative w-2">
<div className="transition-all ease-out ml-0 group-hover:-ml-1">
<IconChevronLeft size={10} strokeWidth={3} />
</div>
</div>
<span>Back to Home</span>
</a>
</Link>
<Link href={menu.url ?? ''} passHref>
<a>
<div className="flex items-center gap-3 my-3">
<Image
alt={menu.icon}
width={15}
height={15}
src={
`${router.basePath}` +
`/img/icons/menu/${menu.icon}${isDarkMode ? '' : '-light'}.svg`
}
/>
{/* </div> */}
<span
className={[
' ',
!menu.title && 'capitalize',
menu.url === router.pathname
? 'text-brand-900'
: 'hover:text-brand-900 text-scale-1200',
].join(' ')}
>
{menu.title ?? level}
</span>
</div>
</a>
</Link>
{menu.items.map((x, index) => {
// console.log('1st type of link?', x.items && x.items.length > 0)
// console.log()
return (
<div key={x.url}>
{x.items && x.items.length > 0 ? (
<div>
{x.items.map((subItem, subItemIndex) => {
// console.log('subitem', { subItem })
// console.log('router', router)
//console.log('subitem url', subItem.url)
let subItemMenuOpen = false
if (router.asPath.includes(subItem.url)) {
subItemMenuOpen = true
}
const LinkContainer = (props) => {
return (
<Link href={props.url} passHref>
<a className={props.className}>{props.children}</a>
</Link>
)
}
return (
<>
{subItemIndex === 0 && (
<>
<div className="h-px w-full bg-green-500 my-3"></div>
<span className="font-mono text-xs uppercase text-scale-1200 font-medium tracking-wider">
{x.name}
</span>
</>
)}
<Accordion.Item key={subItem.label} value={subItem.url}>
<li key={subItem.name}>
<LinkContainer
url={subItem.url}
className={[
'flex items-center gap-2',
'cursor-pointer transition text-sm',
subItem.url === router.pathname
? 'text-brand-900'
: 'hover:text-brand-900 text-scale-1000',
].join(' ')}
parent={subItem.parent}
>
{subItem.icon && (
<Image
alt={subItem.name + router.basePath}
src={
`${router.basePath}` +
`${subItem.icon}${!isDarkMode ? '-light' : ''}.svg`
}
width={15}
height={15}
/>
)}
{subItem.name}
</LinkContainer>
</li>
{subItem.items && subItem.items.length > 0 && (
<Accordion.Content className="transition data-open:animate-slide-down data-closed:animate-slide-up ml-2">
{subItem.items.map((subSubItem, subSubItemIndex) => {
return (
<li key={subItem.name}>
<Link href={`${subSubItem.url}`} passHref>
<a
className={[
'cursor-pointer transition text-sm',
subSubItem.url === router.pathname
? 'text-brand-900'
: 'hover:text-brand-900 text-scale-1000',
].join(' ')}
>
{subSubItem.name}
</a>
</Link>
</li>
)
})}
</Accordion.Content>
)}
</Accordion.Item>
</>
)
})}
</div>
) : (
<>
<li>
<Link href={`${x.url}`} passHref>
<a
className={[
'cursor-pointer transition text-sm',
x.url === router.pathname
? 'text-brand-900'
: 'hover:text-brand-900 text-scale-1000',
].join(' ')}
>
{x.icon && <img className="w-3" src={`${router.basePath}${x.icon}`} />}
{x.name}
</a>
</Link>
</li>
</>
)}
</div>
)
})}
{menu.extras && (
<>
<div className="h-px w-full bg-green-500 my-3"></div>
<span className="font-mono text-xs uppercase text-scale-1200 font-medium tracking-wider mb-2">
Resources
</span>
</>
)}
{menu.extras?.map((x) => {
return (
<div key={x.name}>
<li>
<Link href={`${x.href}`} passHref>
<a className="cursor-pointer transition text-scale-1100 text-sm hover:text-brand-900 flex gap-3 my-1">
{x.icon && <img className="w-4" src={`${router.basePath}${x.icon}`} />}
{x.name}
</a>
</Link>
</li>
</div>
)
})}
</ul>
</Accordion.Root>
)
}
export default NavigationMenuGuideList
@@ -0,0 +1,307 @@
import * as Accordion from '@radix-ui/react-accordion'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { IconChevronLeft } from 'ui'
import * as NavItems from './NavigationMenu.constants'
import { find } from 'lodash'
import Image from 'next/image'
import { useTheme } from 'common/Providers'
// import apiCommonSections from '~/../../spec/common-client-libs-sections.json'
import RevVersionDropdown from '~/components/RefVersionDropdown'
import { useMenuActiveRefId, useMenuLevelId } from '~/hooks/useMenuState'
import { RefIdOptions, RefKeyOptions } from './NavigationMenu'
const FunctionLink = ({
title,
id,
icon,
library,
slug,
}: {
title: string
name?: string
id: string
icon?: string
product?: string
library: string
slug: string
}) => {
const router = useRouter()
const activeAccordianItem = useMenuActiveRefId()
// check if we're on a versioned page
let version = ''
if (router.asPath.includes('v1')) {
version = 'v1'
}
if (router.asPath.includes('v0')) {
version = 'v0'
}
const active = activeAccordianItem === id
return (
<li key={id} className="function-link-item">
<Link href={`/reference/${library}/${version ? version + '/' : ''}${slug}`} passHref>
<a
className={[
'cursor-pointer transition text-sm hover:text-brand-900 flex gap-3',
active ? 'text-brand-900' : 'text-scale-1000',
].join(' ')}
>
{icon && <Image width={16} height={16} alt={icon} src={`${router.basePath}${icon}`} />}
{title}
</a>
</Link>
</li>
)
}
const SideMenuTitle = ({ title }: { title: string }) => {
return (
<span className="font-mono text-xs uppercase text-scale-1200 font-medium tracking-wider mb-3">
{title}
</span>
)
}
const Divider = () => {
return <div className="h-px w-full bg-blackA-300 dark:bg-whiteA-300 my-3"></div>
}
interface INavigationMenuRefList {
id: RefIdOptions
lib: RefKeyOptions
commonSections: any[] // to do type up
// the keys of menu items that are allowed to be shown on the side menu
// if undefined, we show all the menu items
allowedClientKeys?: string[]
}
const NavigationMenuRefList: React.FC<INavigationMenuRefList> = ({
id,
lib,
commonSections,
allowedClientKeys,
}) => {
const router = useRouter()
const { isDarkMode } = useTheme()
let sections = commonSections
const allowedKeys = allowedClientKeys
if (!sections) console.error('no common sections imported')
const menu = NavItems[id]
const databaseFunctions = find(sections, { title: 'Database' })
? find(sections, { title: 'Database' }).items
: []
const authFunctions = find(sections, { title: 'Auth' })
? find(sections, { title: 'Auth' }).items
: []
const filterIds =
databaseFunctions.length > 0
? find(databaseFunctions, {
id: 'using-filters',
}) &&
find(databaseFunctions, {
id: 'using-filters',
})
.items.filter((x) => allowedKeys.includes(x.id))
.map((x) => x.id)
: []
const modifierIds =
databaseFunctions.length > 0
? find(databaseFunctions, {
id: 'using-modifiers',
}) &&
find(databaseFunctions, {
id: 'using-modifiers',
})
.items.filter((x) => allowedKeys.includes(x.id))
.map((x) => x.id)
: []
const authServerIds =
databaseFunctions.length > 0
? find(authFunctions, {
id: 'admin-api',
}) &&
find(authFunctions, {
id: 'admin-api',
}).items.map((x) => x.id)
: []
// console.log(filterIds)
// console.log(modifierIds)
const level = useMenuLevelId()
return (
<div
className={[
'transition-all ml-8 duration-150 ease-out',
// enabled
level === id && 'opacity-100 ml-0 delay-150 h-auto',
// move menu back to margin-left
level === 'home' && 'ml-12',
// disabled
level !== 'home' && level !== id ? '-ml-8' : '',
level !== id ? 'opacity-0 invisible absolute h-0 overflow-hidden' : '',
].join(' ')}
>
<div className={'w-full flex flex-col gap-0 sticky top-8'}>
<Link href="/" passHref>
<a
className={[
'flex items-center gap-1 text-xs group mb-3',
'text-base transition-all duration-200 text-scale-1100 hover:text-brand-1200 hover:cursor-pointer ',
].join(' ')}
>
<div className="relative w-2">
<div className="transition-all ease-out ml-0 group-hover:-ml-1">
<IconChevronLeft size={10} strokeWidth={3} />
</div>
</div>
<span>Back to Main Menu</span>
</a>
</Link>
<div className="flex items-center gap-3 my-3">
<Image
alt={id}
width={24}
height={24}
src={`${router.basePath}/img/icons/menu/${menu.icon}${isDarkMode ? '' : '-light'}.svg`}
className="rounded"
/>
<span className={['text-base text-brand-1200 ', !menu.title && 'capitalize'].join(' ')}>
{menu.title}
</span>
<RevVersionDropdown />
</div>
{/* )} */}
<ul className="function-link-list">
{sections.map((fn: any, fnIndex) => {
//
// check if the link is allowed to be displayed
function isFuncNotInLibraryOrVersion(id, type) {
if (id && allowedKeys && !allowedKeys.includes(id) && type !== 'markdown') {
/*
* Remove this menu link from side bar, as it does not exist for either
* this language, or for this lib version
*
* */
return true
} else {
return false
}
}
// run allow check
if (isFuncNotInLibraryOrVersion(fn.id, fn.type)) {
return <></>
}
const RenderLink = (props) => {
const activeAccordianItem = useMenuActiveRefId()
let active = false
const isFilter =
filterIds && filterIds.length > 0 && filterIds.includes(activeAccordianItem)
const isModifier =
modifierIds && modifierIds.length > 0 && modifierIds.includes(activeAccordianItem)
const isAuthServer =
authServerIds &&
authServerIds.length > 0 &&
authServerIds.includes(activeAccordianItem)
if (
(isFilter && !isModifier && !isAuthServer && props.id === 'using-filters') ||
(activeAccordianItem === 'using-filters' && props.id === 'using-filters')
) {
active = true
} else if (
(isModifier && !isFilter && !isAuthServer && props.id === 'using-modifiers') ||
(activeAccordianItem === 'using-modifiers' && props.id === 'using-modifiers')
) {
active = true
} else if (
(isAuthServer && !isFilter && !isModifier && props.id === 'admin-api') ||
(activeAccordianItem === 'admin-api' && props.id === 'admin-api')
) {
active = true
} else {
active = false
}
return (
<Accordion.Root
collapsible
key={props.id + '-accordian-root-for-func-' + fnIndex}
type="single"
value={active ? props.id : ''}
>
<Accordion.Item key={props.id + '-accordian-item'} value={props.id}>
<FunctionLink {...props} library={lib} />
<Accordion.Content
key={props.id + '-sub-items-accordion-container'}
className="transition data-open:animate-slide-down data-closed:animate-slide-up ml-2"
>
{props.items &&
props.items
.filter((item) => allowedKeys.includes(item.id))
.map((item) => {
return <FunctionLink {...item} library={lib} />
})}
</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
)
}
// handle subtitles with subitems
if (!fn.id) {
return (
<>
<Divider />
<SideMenuTitle title={fn.title} />
{fn.items &&
fn.items
//.filter((item) => item.libs && item.libs.includes(lib))
.map((item) => {
// run allow check
if (isFuncNotInLibraryOrVersion(item.id, item.type)) return <></>
return <RenderLink {...item} library={menu.title} />
})}
</>
)
} else {
// handle normal links
return (
<>
<RenderLink {...fn} library={menu.title} />
{fn.items &&
fn.items
//.filter((item) => item.libs.includes(lib))
.map((item) => <RenderLink {...item} library={menu.title} />)}
</>
)
}
})}
</ul>
</div>
</div>
)
}
export default NavigationMenuRefList
@@ -0,0 +1,172 @@
import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { FC, useEffect, useState } from 'react'
import { Button, IconCommand, IconMenu, IconMoon, IconSearch, IconSun, Input, Listbox } from 'ui'
import { SearchButton } from '~/components/DocSearch'
import { REFERENCES } from '~/components/Navigation/Navigation.constants'
import { useTheme } from 'common/Providers'
import { getPageType } from '~/lib/helpers'
const TopNavBar: FC = () => {
const { isDarkMode, toggleTheme } = useTheme()
const [mounted, setMounted] = useState(false)
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const { asPath, push } = useRouter()
const pathSegments = asPath.split('/')
const library = pathSegments.length >= 3 ? pathSegments[2] : undefined
const libraryMeta = REFERENCES?.[library] ?? undefined
const versions = libraryMeta?.versions ?? []
const version = versions.includes(pathSegments[pathSegments.indexOf(library) + 1])
? pathSegments[pathSegments.indexOf(library) + 1]
: versions[0]
const pageType = getPageType(asPath)
useEffect(() => {
setMounted(true)
}, [isDarkMode])
const pageLinks = [
{ text: 'Guides', key: 'docs', link: '/' },
{ text: 'Reference', key: 'reference', link: '/reference' },
]
const toggleDarkMode = () => {
localStorage.setItem('supabaseDarkMode', (!isDarkMode).toString())
toggleTheme()
const key = localStorage.getItem('supabaseDarkMode')
document.documentElement.className = key === 'true' ? 'dark' : ''
}
const onSelectVersion = (version: string) => {
// [Joshen] Ideally we use <Link> but this works for now
if (!library) return
if (version === versions[0]) {
push(`/reference/${library}`)
} else {
push(`/reference/${library}/${version}`)
}
}
// [Joshen] Kaizen: Use UI library's SidePanel for this
const toggleMobileMenu = () => {
setMobileMenuOpen(!mobileMenuOpen)
const sidebar = document.querySelector('.sidebar-menu-container')
const contentPane = document.querySelector('.main-content-pane')
sidebar.classList.toggle('hidden')
contentPane.classList.toggle('hidden')
}
return (
<nav className="h-[60px] border-b px-4 backdrop-blur backdrop-filter bg-white-1200 dark:bg-blackA-300">
<div className="max-w-[1400px] grid grid-cols-12 mx-auto gap-4 px-5 h-full">
<div className="col-span-3 flex items-center">
<button className="mr-4 block stroke-2 lg:hidden" onClick={toggleMobileMenu}>
<IconMenu className="text-scale-1100" />
</button>
{mounted && (
<Link href="/">
<a className="flex items-center">
<Image
className="cursor-pointer"
src={isDarkMode ? '/docs/supabase-dark.svg' : '/docs/supabase-light.svg'}
width={124}
height={24}
alt="Supabase Logo"
/>
</a>
</Link>
)}
{versions.length > 0 && (
<div className="ml-8">
<Listbox
size="small"
defaultValue={version}
style={{ width: '70px' }}
onChange={onSelectVersion}
>
{versions.map((version) => (
<Listbox.Option key={version} label={version} value={version}>
{version}
</Listbox.Option>
))}
</Listbox>
</div>
)}
</div>
<div className="col-span-5 flex items-center">
<div className="max-w-xl grow">
<SearchButton className="w-full">
<div
className="
flex
group
items-center
justify-between
bg-scaleA-200
border
transition
hover:border-scale-600
hover:bg-scaleA-300
border-scale-500 pl-3 pr-1.5 w-full h-[32px] rounded"
>
<div className="flex items-center space-x-2">
<IconSearch className="text-scale-1100" size={18} strokeWidth={2} />
<p className="text-scale-1100 text-sm group-hover:text-scale-1200 transition">
Search docs...
</p>
</div>
<div className="flex items-center space-x-1">
<div className="text-scale-1200 md:flex items-center justify-center h-5 w-10 border rounded bg-scale-500 border-scale-700 gap-1">
<IconCommand size={12} strokeWidth={1.5} />
<span className="text-[12px]">K</span>
</div>
</div>
</div>
</SearchButton>
</div>
</div>
<div className="col-span-4 flex items-center justify-end gap-3">
<Button
type="outline"
as="a"
// @ts-ignore
href="https://app.supabase.com"
className="text-scale-1100 text-sm"
target="_blank"
rel="noreferrer noopener"
>
Go to Dashboard
</Button>
<ul className="flex items-center">
<li className="px-4">
<div className="cursor-pointer" onClick={toggleDarkMode}>
{isDarkMode ? (
<IconMoon
size={16}
strokeWidth={1}
className="text-scale-1100 hover:text-scale-1200 transition"
/>
) : (
<IconSun
size={16}
strokeWidth={1}
className="text-scale-1100 hover:text-scale-1200 transition"
/>
)}
</div>
</li>
</ul>
</div>
</div>
</nav>
)
}
export default TopNavBar
@@ -0,0 +1,173 @@
import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { FC, useEffect, useState } from 'react'
import {
Badge,
Button,
Dropdown,
IconCommand,
IconMenu,
IconMessageSquare,
IconMoon,
IconSearch,
IconSun,
Input,
Listbox,
} from 'ui'
import { SearchButton } from '~/components/DocSearch'
import { REFERENCES } from '~/components/Navigation/Navigation.constants'
import { useTheme } from 'common/Providers'
import { getPageType } from '~/lib/helpers'
const TopNavBarRef: FC = () => {
const { isDarkMode, toggleTheme } = useTheme()
const [mounted, setMounted] = useState(false)
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const { asPath, push } = useRouter()
const pathSegments = asPath.split('/')
const library = pathSegments.length >= 3 ? pathSegments[2] : undefined
const libraryMeta = REFERENCES?.[library] ?? undefined
const versions = libraryMeta?.versions ?? []
const version = versions.includes(pathSegments[pathSegments.indexOf(library) + 1])
? pathSegments[pathSegments.indexOf(library) + 1]
: versions[0]
const pageType = getPageType(asPath)
useEffect(() => {
setMounted(true)
}, [isDarkMode])
const pageLinks = [
{ text: 'Guides', key: 'docs', link: '/' },
{ text: 'Reference', key: 'reference', link: '/reference' },
]
const toggleDarkMode = () => {
localStorage.setItem('supabaseDarkMode', (!isDarkMode).toString())
toggleTheme()
const key = localStorage.getItem('supabaseDarkMode')
document.documentElement.className = key === 'true' ? 'dark' : ''
}
const onSelectVersion = (version: string) => {
// [Joshen] Ideally we use <Link> but this works for now
if (!library) return
if (version === versions[0]) {
push(`/reference/${library}`)
} else {
push(`/reference/${library}/${version}`)
}
}
// [Joshen] Kaizen: Use UI library's SidePanel for this
const toggleMobileMenu = () => {
setMobileMenuOpen(!mobileMenuOpen)
const sidebar = document.querySelector('.sidebar-menu-container')
const contentPane = document.querySelector('.main-content-pane')
sidebar.classList.toggle('hidden')
contentPane.classList.toggle('hidden')
}
return (
<nav className="h-[60px] border-b backdrop-blur backdrop-filter bg-white-1200 dark:bg-blackA-300">
<div className="px-5 max-w-7xl mx-auto flex gap-3 justify-between items-center h-full">
<div className={['lg:hidden'].join(' ')}>
<Link href="/">
<a className=" flex items-center gap-2">
<Image
className="cursor-pointer"
src={isDarkMode ? '/docs/supabase-dark.svg' : '/docs/supabase-light.svg'}
width={96}
height={24}
alt="Supabase Logo"
/>
<span className="font-mono text-sm font-medium text-brand-900">DOCS</span>
</a>
</Link>
{/* {router.asPath.includes('/reference/') && <RefSwitcher />} */}
</div>
<div className="flex items-center gap-12">
<SearchButton className="w-full lg:w-96">
<div
className="
flex
group
items-center
justify-between
bg-scaleA-200
border
transition
hover:border-scale-600
hover:bg-scaleA-300
border-scale-500 pl-1.5 md:pl-3 pr-1.5 w-full h-[32px] rounded"
>
<div className="flex items-center space-x-2">
<IconSearch className="text-scale-1100" size={18} strokeWidth={2} />
<p className="hidden md:flex text-scale-1100 text-sm group-hover:text-scale-1200 transition">
Search docs...
</p>
</div>
<div className="hidden md:flex items-center space-x-1">
<div className="text-scale-1200 md:flex items-center justify-center h-5 w-10 border rounded bg-scale-500 border-scale-700 gap-1">
<IconCommand size={12} strokeWidth={1.5} />
<span className="text-[12px]">K</span>
</div>
</div>
</div>
</SearchButton>
</div>
<div className="hidden lg:flex grow items-center justify-end gap-3">
<Button
type="text"
as="a"
// @ts-ignore
href="https://supabase.com"
target="_blank"
rel="noreferrer noopener"
>
Main site
</Button>
<Button
type="text"
as="a"
// @ts-ignore
href="https://app.supabase.com"
target="_blank"
rel="noreferrer noopener"
>
Dashboard
</Button>
<ul className="flex items-center">
<li className="px-4">
<div className="cursor-pointer" onClick={toggleDarkMode}>
{isDarkMode ? (
<IconMoon
size={16}
strokeWidth={1}
className="text-scale-1100 hover:text-scale-1200 transition"
/>
) : (
<IconSun
size={16}
strokeWidth={1}
className="text-scale-1100 hover:text-scale-1200 transition"
/>
)}
</div>
</li>
</ul>
</div>
</div>
</nav>
)
}
export default TopNavBarRef
@@ -0,0 +1,174 @@
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import { Badge, Dropdown, GlassPanel, IconChevronDown, IconChevronRight, Popover } from 'ui'
import { references } from './NavigationMenu/NavigationMenu.constants'
const RefSwitcher = () => {
const router = useRouter()
const [currentRef, setCurrentRef] = useState()
const [open, setOpen] = useState(false)
useEffect(() => {
setOpen(false)
}, [router.pathname])
return (
<div className="px-10 flex items-center -space-x-px">
<Popover
size="content"
open={open}
onOpenChange={() => setOpen(!open)}
overlay={
<>
{references.map((section) => {
return (
<>
<div className="w-full px-5 py-1 border-b border-scale-400 bg-whiteA-200">
<span className="text-xs font-mono uppercase tracking-wider font-medium text-scale-1000">
{section.label}
</span>
</div>
<div className="px-5 py-8 grid grid-cols-12 gap-8">
{section.items.map((item) => {
return (
<Link href={item.url}>
<a
className="flex gap-3 col-span-4 group relative cursor-pointer items-start min-w-[180px]"
onClick={() => setOpen(!open)}
>
<div className="flex justify-between gap-3">
<img
src={item.icon}
className="
mt-1 w-5 h-5 rounded
transition-all
ease-out
opacity-80 group-hover:opacity-100
scale-95 group-hover:scale-100
"
/>
<div className="flex flex-col">
<div className="transition text-base text-scale-1100 group-hover:text-scale-1200 flex items-center">
<span>{item.label}</span>
<div className="relative h-4 w-4">
<div
className="
absolute
top-0.5
transition-all
ease-out
opacity-0 group-hover:opacity-100
scale-95 group-hover:scale-100
left-0 group-hover:left-1
"
>
<IconChevronRight strokeWidth={1.5} size={14} />
</div>
</div>
</div>
<span className="text-xs text-scale-1000">{item.description}</span>
</div>
</div>
</a>
</Link>
)
})}
</div>
</>
)
})}
</>
}
>
<div
className="
flex
group
items-center
justify-between
bg-scaleA-200
border
transition
hover:border-scale-600
hover:bg-scaleA-300
border-scale-500 pl-3 pr-1.5 w-full h-[32px] rounded
rounded-tr-none
rounded-br-none
"
>
<div className="flex items-center justify-between w-full min-w-[128px]">
<div className="flex items-center space-x-2">
<img
src="/docs/img/icons/javascript-icon.svg"
className="w-4 h-4 rounded drop-shadow-sm"
/>
<p className="text-scale-1200 text-sm group-hover:text-scale-1200 transition">
supabase-js
</p>
</div>
<div className="text-scale-900">
<IconChevronDown size={14} strokeWidth={1.5} />
</div>
</div>
</div>
</Popover>
<Dropdown size="small" align="start" side="bottom" overlay={<VersionOverlay />}>
<div className="w-20">
<div
className="
flex
group
items-center
justify-between
bg-scaleA-200
border
transition
hover:border-scale-600
hover:bg-scaleA-300
border-scale-500 pl-3 pr-1.5 w-full h-[32px] rounded
rounded-tl-none
rounded-bl-none
"
>
<div className="flex items-center justify-between w-full">
<div className="flex items-center space-x-2">
<p className="font-mono text-scale-1200 text-xs group-hover:text-scale-1200 transition">
v2.0
</p>
</div>
<div className="text-scale-900">
<IconChevronDown size={14} strokeWidth={1.5} />
</div>
</div>
</div>
</div>
</Dropdown>
</div>
)
}
const VersionOverlay = () => {
return (
<>
<Dropdown.Label>Stable releases</Dropdown.Label>
<Dropdown.Item>
version 2.0
<Dropdown.RightSlot>
<Badge size="small">Latest</Badge>
</Dropdown.RightSlot>
</Dropdown.Item>
<Dropdown.Item>Version 1.0 </Dropdown.Item>
<Dropdown.Separator />
<Dropdown.Label>Alpha releases</Dropdown.Label>
<Dropdown.Misc>
<p className="text-scale-900 text-xs">Currently no alpha releases</p>
</Dropdown.Misc>
</>
)
}
export default RefSwitcher
+81
View File
@@ -0,0 +1,81 @@
import { FC, useState } from 'react'
import { IconXCircle } from '~/../../packages/ui'
interface IOptions {
name?: string
}
type IOption = any
type OptionsSubComponents = {
Option: IOption
}
const Options: FC<IOptions> & OptionsSubComponents = (props) => {
const [open, setOpen] = useState(false)
return (
<div className="mt-0">
<button
className={[
'px-5',
'border-t border-l border-r border-scale-500',
'text-left text-sm text-scale-1100',
'hover:bg-scale-300 transition-all',
'flex items-center gap-2',
open ? 'w-full py-1.5 rounded-tl-lg rounded-tr-lg' : 'py-1 border-b rounded-full',
].join(' ')}
onClick={() => setOpen(!open)}
>
<div className="">
<div className={[!open ? 'rotate-45' : 'rotate-0'].join(' ')}>
<IconXCircle size={14} />
</div>
</div>
{`${!open ? `Open` : `Close`} ${props.name ?? 'accepted values'}`}
</button>
<div
className={['transition-all opacity-0', open ? 'opacity-100 h-auto' : 'invisible h-0'].join(
' '
)}
>
{props.children}
</div>
</div>
)
}
const Option: FC<IOption> = (props) => {
return (
<div
className="
px-5 py-3 first:border-t border-b border-l border-r
border-scale-500
last:rounded-bl-lg last:rounded-br-lg
flex flex-col gap-3
"
>
<div className="flex gap-3 items-center">
<span className="text-sm text-scale-1200 font-mono font-medium">
{props.name ?? 'no-name'}
</span>
<span>
{props.isOptional ? (
<div className="text-[10px] px-3 tracking-wide font-mono text-scale-900">Optional</div>
) : (
<div className="text-[10px] border border-amber-700 bg-amber-300 text-amber-900 px-2 tracking-wide font-mono py-0.25 rounded-full">
REQUIRED
</div>
)}
</span>
<span className="text-scale-900 text-xs">{props.type ?? 'no type'}</span>
</div>
<p className="text-sm text-scale-1000 m-0">{props.description ?? 'nodescription'}</p>
{props.children}
</div>
)
}
Options.Option = Option
export default Options
+29
View File
@@ -0,0 +1,29 @@
import { FC } from 'react'
type IParamProps = any
const Param: FC<IParamProps> = (paramItem) => {
return (
<li className="border-t border-b py-5 flex flex-col gap-3">
<div className="flex gap-3 items-center">
<span className="text-sm text-scale-1200 font-mono font-medium">
{paramItem.name ?? 'no-name'}
</span>
<span>
{paramItem.isOptional ? (
<div className="text-[10px] px-3 tracking-wide font-mono text-scale-900">Optional</div>
) : (
<div className="text-[10px] border border-amber-700 bg-amber-300 text-amber-900 px-2 tracking-wide font-mono py-0.25 rounded-full">
REQUIRED
</div>
)}
</span>
<span className="text-scale-900 text-xs">{paramItem.type ?? 'no type'}</span>
</div>
<p className="text-sm text-scale-1000 m-0">{paramItem.description ?? 'nodescription'}</p>
{paramItem.children}
</li>
)
}
export default Param
@@ -0,0 +1,73 @@
import { Badge, Dropdown, IconChevronDown } from 'ui'
import { useRouter } from 'next/router'
import { REFERENCES } from './Navigation/NavigationMenu/NavigationMenu.constants'
const RevVersionDropdown = () => {
const { asPath, push } = useRouter()
const pathSegments = asPath.split('/')
const library = pathSegments.length >= 3 ? pathSegments[2] : undefined
const libraryMeta = REFERENCES?.[library] ?? undefined
const versions = libraryMeta?.versions ?? []
const currentVersion = versions.includes(pathSegments[pathSegments.indexOf(library) + 1])
? pathSegments[pathSegments.indexOf(library) + 1]
: versions[0]
const onSelectVersion = (version: string) => {
if (!library) return
if (version === versions[0]) {
push(`/reference/${library}/start`)
} else {
push(`/reference/${library}/${version}/start`)
}
}
if (!versions || versions.length === 0) {
return <></>
}
return (
<Dropdown
size="small"
align="start"
side="bottom"
overlay={
<>
<Dropdown.Label>Stable releases</Dropdown.Label>
{versions.map((version, index) => (
<Dropdown.Item key={version} onClick={() => onSelectVersion(version)}>
<span className={`${currentVersion === version ? 'font-bold' : ''}`}>
Version {version}.0
</span>
<Dropdown.RightSlot>
{index === 0 && <Badge size="small">Latest</Badge>}
</Dropdown.RightSlot>
</Dropdown.Item>
))}
</>
}
>
<div
className="
group
justify-between
bg-scaleA-200
border
hover:border-scale-600
hover:bg-scaleA-300
border-scale-500 px-2 h-[32px] rounded
font-mono
flex items-center gap-1 text-scale-900 text-xs group-hover:text-scale-1200 transition
"
>
{/* <span>version</span> */}
<span className="text-scale-1200 text-sm group-hover:text-scale-1200 transition">
{currentVersion}.0
</span>
<IconChevronDown size={14} strokeWidth={2} />
</div>
</Dropdown>
)
}
export default RevVersionDropdown
@@ -0,0 +1,8 @@
import { createContext } from 'react'
// Make sure the shape of the default value passed to
// createContext matches the shape that the consumers expect!
export const StepHikeContext = createContext({
activeStep: undefined,
steps: undefined,
})
+187
View File
@@ -0,0 +1,187 @@
import { useEffect, useState } from 'react'
import { useInView } from 'react-intersection-observer'
import { StepHikeContext } from './StepHikeContext'
const StepHike = ({ children, title }) => {
const [activeStep, setActiveStep] = useState(undefined)
// check if there are any children
if (!children) throw 'StepHike component requires <StepHike.Step> children'
const steps = children.filter((x) => {
return x.type.name === 'Step'
})
useEffect(() => {
setActiveStep({
titleId: steps[0].props.title.replaceAll(' ', '-').toLowerCase(),
step: 0,
})
}, [])
// check if there is at least 1 StepHike subcomponent
if (steps.length === 0 || !steps)
throw 'StepHike component needs at least 1 <StepHike.Step> child'
// console.log('length of the steps filter', steps.length)
return (
<div className="">
{/* <div
className="
sticky w-full top-[96px] z-50 p-5 rounded-lg
flex gap-3 items-center
bg-white-1200 dark:bg-scale-200
border-t border-l border-r border-scale-600
h-[60px]
not-prose
rounded-bl-none
rounded-br-none
shadow-md
-mb-1.5
"
>
<h3 className="text-scale-1200 text-xl">{title}</h3>
<div className="justify-end grow flex gap-3">
<Button type="default" onClick={() => handlePrev()} disabled={activeStep?.step === 0}>
Prev
</Button>
<Button
type="default"
onClick={() => handleNext()}
disabled={steps.length - 1 === activeStep?.step}
>
Next
</Button>
</div>
</div> */}
{/* <div
className="sticky w-full top-[128px] bottom-[64px] z-10 p-5 rounded-lg
flex gap-3 items-center
backdrop-blur-lg backdrop-filter bg-white-1200 dark:bg-whiteA-300
h-[60px]
not-prose
"
>
<div className="flex items-center gap-6">
<div className="border bg-scale-600 border-scale-700 flex w-7 h-7 items-center justify-center rounded text-base text-scale-1200 font-semibold font-mono">
{activeStep?.step + 1}
</div>
<h3 className="text-scale-1200 text-xl">
{steps[activeStep?.step] && steps[activeStep?.step].props.title}
</h3>
<span className="font-mono uppercase text-xs">
Step {activeStep?.step + 1} of {steps?.length}
</span>
</div>
<div className="justify-end grow flex gap-3">
<Button type="default" onClick={() => handlePrev()} disabled={activeStep?.step === 0}>
Prev
</Button>
<Button
type="default"
onClick={() => handleNext()}
disabled={steps.length - 1 === activeStep?.step}
>
Next
</Button>
</div>
</div> */}
<StepHikeContext.Provider value={{ activeStep, steps }}>{children}</StepHikeContext.Provider>
</div>
)
}
const Step = ({ children, title, step }) => {
const [show, setShow] = useState(false)
useEffect(() => {
setShow(true)
}, [])
const ChildrenRender = ({ active }) => <div className="pl-[74px]">{children}</div>
// const ref = useRef<HTMLDivElement | null>(null)
// useEffect(() => {
// const cachedRef = ref.current
// const observer = new IntersectionObserver(
// ([e]) =>
// // setStuck(e.intersectionRatio < 1)
// console.log('scroll', title),
// {
// threshold: [0],
// rootMargin: '0px 0px 0px 0px',
// }
// )
// // const cachedRef: HTMLDivElement | null
// // Argument of type 'HTMLDivElement | null' is not assignable to parameter of type 'Element'.
// // Type 'null' is not assignable to type 'Element'.ts(2345)
// observer.observe(cachedRef)
// return () => observer.unobserve(cachedRef)
// }, [ref])
const { ref } = useInView({
rootMargin: '10px 20px 30px 40px',
threshold: 1,
onChange: (inView, entry) => {
if (window.scrollY === 0) console.log('out of view', title)
if (inView) console.log('in view', title) // highlightSelectedTocItem(entry.target.id)
},
})
return (
<>
<StepHikeContext.Consumer>
{({ activeStep, steps }) => {
// console.log('activeStep', activeStep)
const cleanTitleId = title.replaceAll(' ', '-').toLowerCase()
const active = cleanTitleId === activeStep?.titleId
// useEffect(() => {}, [])
return (
<div>
{/* <div className="h-[2px] w-full bg-scale-500"></div> */}
<div
ref={ref}
className="sticky w-full top-[64px] z-10 p-5 rounded-lg
flex gap-3 items-center
backdrop-blur-lg backdrop-filter bg-white-1200 dark:bg-whiteA-300
border-b border-l border-r border-scale-600 border-t
h-[60px]
not-prose
shadow-md
"
>
<div className="flex items-center gap-6">
<div className="border bg-scale-600 border-scale-700 flex w-7 h-7 items-center justify-center rounded text-base text-scale-1200 font-semibold font-mono">
{step}
</div>
<h3 className="text-scale-1200 text-xl" id={cleanTitleId}>
{title}
</h3>
<span className="font-mono uppercase text-xs">
Step {step} of {steps?.length}
</span>
</div>
</div>
<ChildrenRender active={active} />
</div>
)
}}
</StepHikeContext.Consumer>
</>
)
}
StepHike.Step = Step
export default StepHike
@@ -0,0 +1,8 @@
import { createContext } from 'react'
// Make sure the shape of the default value passed to
// createContext matches the shape that the consumers expect!
export const StepHikeContext = createContext({
activeStep: undefined,
steps: undefined,
})
@@ -0,0 +1,93 @@
//import { Step } from 'next-seo/lib/types'
import React, { FC } from 'react'
interface IStep {
title: string
step: number | string
}
interface IStepHikeCompactSubcomponents {
Step: FC<IStep>
Details: FC<IDetails>
Code: FC<ICode>
}
interface IDetails {
title?: string
}
interface ICode {}
interface IStepHikeCompact {
title: string
}
const StepHikeCompact: FC<IStepHikeCompact> & IStepHikeCompactSubcomponents = ({
children,
title,
}) => {
return <div className="py-8">{children}</div>
}
const Step: FC<IStep> = ({ children, title, step }) => {
return (
<div className="relative pb-8">
<div
className="
absolute
w-px
left-[11px]
pt-1
h-full
"
>
<div
className="
absolute
w-full
h-full
py-1
bg-scale-400
"
></div>
</div>
<div
className="
absolute
left-0
flex gap-3 items-center
not-prose
"
>
<div className="flex items-center gap-6">
<div
className="border bg-white dark:bg-scale-400
border-scale-600 flex items-center justify-center rounded-full
w-6 h-6 text-xs text-scale-1200 font-normal font-mono
dropshadow-sm
"
>
{step}
</div>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-12 lg:gap-10 lg:ml-12">{children}</div>
</div>
)
}
const Details: FC<IDetails> = ({ children, title }) => {
return (
<div className="col-span-5 ml-12 lg:ml-0">
<h3 className="mt-0 text-scale-1200 text-base">{title}</h3>
{children}
</div>
)
}
const Code: FC<ICode> = ({ children }) => {
return <div className="col-span-7 not-prose">{children}</div>
}
StepHikeCompact.Step = Step
StepHikeCompact.Details = Details
StepHikeCompact.Code = Code
export default StepHikeCompact
+29 -16
View File
@@ -15,6 +15,7 @@ interface TOCHeader {
interface Props {
toc: any
video?: string
}
const formatSlug = (slug: string) => {
@@ -44,26 +45,38 @@ const formatTOCHeader = (content: string) => {
return res.join('')
}
const TableOfContents: FC<Props> = ({ toc }) => {
const TableOfContents: FC<Props> = ({ toc, video }) => {
// [Joshen] markdown-toc doesn't seem to read maxdepth from the options passed in
// Our first level headers will be H2s (H1 is ignored), and we only show up to H3
return (
<ul className="toc-menu list-none pl-4 text-[0.8rem] grid gap-2 mt-12">
{(toc.json as TOCHeader[])
.filter((item) => item.lvl !== 1 && item.lvl <= 3)
.map((item: any, i: number) => {
return (
<li key={i} id={item.lvl} style={{ marginLeft: `${(item.lvl - 2) * 1}rem` }}>
<a
href={`#${formatSlug(item.slug)}`}
className="text-scale-1000 hover:text-brand-900 transition-colors"
dangerouslySetInnerHTML={{ __html: formatTOCHeader(removeAnchor(item.content)) }}
/>
</li>
)
})}
</ul>
<>
{video && (
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/0Fs96oZ4se0"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
)}
<ul className="toc-menu list-none pl-4 text-[0.8rem] grid gap-2 mt-12">
{(toc.json as TOCHeader[])
.filter((item) => item.lvl !== 1 && item.lvl <= 3)
.map((item: any, i: number) => {
return (
<li key={i} id={item.lvl} style={{ marginLeft: `${(item.lvl - 2) * 1}rem` }}>
<a
href={`#${formatSlug(item.slug)}`}
className="text-scale-1000 hover:text-brand-900 transition-colors"
dangerouslySetInnerHTML={{ __html: formatTOCHeader(removeAnchor(item.content)) }}
/>
</li>
)
})}
</ul>
</>
)
}
+17 -6
View File
@@ -19,6 +19,13 @@ import QuickstartIntro from './MDX/quickstart_intro.mdx'
import ProjectSetup from './MDX/project_setup.mdx'
import { Mermaid } from 'mdx-mermaid/lib/Mermaid'
import InlineCodeTag from './CustomHTMLElements/InlineCode'
import React from 'react'
import RefSubLayout from '~/layouts/ref/RefSubLayout'
// import { CH } from '@code-hike/mdx/components'
import RefHeaderSection from './reference/RefHeaderSection'
// Ref version specific
import CliGlobalFlagsHandler from '~/components/reference/enrichments/cli/CliGlobalFlagsHandler'
const components = {
Admonition,
@@ -40,7 +47,6 @@ const components = {
),
Tabs,
TabPanel: (props: any) => <Tabs.Panel {...props}>{props.children}</Tabs.Panel>,
h2: (props: any) => (
<Heading tag="h2" {...props}>
{props.children}
@@ -51,12 +57,17 @@ const components = {
{props.children}
</Heading>
),
code: (props: any) => {
const linesToHighlight = parseNumericRange(props.lines ?? '')
return <CodeBlock {...props} linesToHighlight={linesToHighlight} />
},
// pre: (props: any) => {
// const linesToHighlight = parseNumericRange(props.lines ?? '')
// return <CodeBlock {...props} linesToHighlight={linesToHighlight} />
// },
mono: (props: any) => <code className="text-sm">{props.children}</code>,
inlineCode: (props: any) => <InlineCodeTag>{props.children}</InlineCodeTag>,
// inlineCode: (props: { children: string }) => <InlineCodeTag {...props} />,
RefSubLayout,
// CH,
code: (props: any) => <CodeBlock {...props} />,
RefHeaderSection: (props: any) => <RefHeaderSection {...props} />,
CliGlobalFlagsHandler: () => <CliGlobalFlagsHandler />,
}
export default components
@@ -0,0 +1,115 @@
import { Tabs } from '~/../../packages/ui'
import CodeBlock from '~/components/CodeBlock/CodeBlock'
import Param from '~/components/Params'
import RefSubLayout from '~/layouts/ref/RefSubLayout'
const ApiOperationSection = (props) => {
const operation = props.spec.operations.find((x: any) => x.operationId === props.funcData.id)
// gracefully return nothing if function does not exist
if (!operation) return <></>
return (
<RefSubLayout.Section
key={operation.operationId}
slug={operation.operationId}
title={operation.summary}
id={operation.operationId}
scrollSpyHeader={true}
monoFont={false}
>
<RefSubLayout.Details>
<div className="mt-4">
<code className="text-md flex gap-4 text-md text-scale-900 break-all">
<span
className="
uppercase
whitespace-nowrap
bg-scale-1200 text-scale-100
flex items-center justify-center
rounded-full font-mono font-medium text-xs px-2 py-0.5"
>
{operation.operation}
</span>
{operation.fullPath}
</code>
</div>
<div className="prose dark:prose-dark py-4">
<p>{operation.description}</p>
</div>
{/* Path Parameters */}
{operation.parameters &&
operation.parameters.filter((parameter) => parameter.in === 'path').length > 0 && (
<div className="not-prose mt-12">
<h5 className="mb-3 text-base text-scale-1200">Path Parameters</h5>
<ul className="mt-4">
{operation.parameters &&
operation.parameters
.filter((parameter: any) => parameter.in === 'path')
.map((parameter: any) => (
<Param {...parameter} isOptional={!parameter.required}></Param>
))}
</ul>
</div>
)}
{/* Query Parameters */}
{operation.parameters &&
operation.parameters.filter((parameter) => parameter.in === 'query').length > 0 && (
<div className="not-prose mt-12">
<h5 className="mb-3 text-base text-scale-1200">Query Parameters</h5>
<ul className="mt-4">
{operation.parameters &&
operation.parameters
.filter((parameter: any) => parameter.in === 'query')
.map((parameter: any) => (
<Param {...parameter} isOptional={!parameter.required}></Param>
))}
</ul>
</div>
)}
{/* Header Parameters */}
{operation.parameters &&
operation.parameters.filter((parameter) => parameter.in === 'header').length > 0 && (
<div className="not-prose mt-12">
<h5 className="mb-3 text-base text-scale-1200">Query Parameters</h5>
<ul className="mt-4">
{operation.parameters &&
operation.parameters
.filter((parameter: any) => parameter.in === 'header')
.map((parameter: any) => (
<Param {...parameter} isOptional={!parameter.required}></Param>
))}
</ul>
</div>
)}
</RefSubLayout.Details>
{operation.responseList && operation.responseList.length > 0 && (
<RefSubLayout.Examples>
<h5 className="mb-3 text-base text-scale-1200">Responses</h5>
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId={operation.responseList[0].responseCode}
>
{operation.responseList.map((response: any) => (
<Tabs.Panel id={response.responseCode} label={response.responseCode}>
<p className="text-scale-1000">{response.description}</p>
{response?.content && response?.content['application/json'] && (
<div className="mt-8">
<CodeBlock language="bash" className="relative">
{JSON.stringify(response.content['application/json'], null, 2)}
</CodeBlock>
</div>
)}
</Tabs.Panel>
))}
</Tabs>
</RefSubLayout.Examples>
)}
</RefSubLayout.Section>
)
}
export default ApiOperationSection
@@ -0,0 +1,140 @@
import { IconChevronRight } from '~/../../packages/ui'
// @ts-expect-error
import spec from '~/../../spec/cli_v1_commands.yaml' assert { type: 'yml' }
import CodeBlock from '~/components/CodeBlock/CodeBlock'
import Options from '~/components/Options'
import Param from '~/components/Params'
import RefSubLayout from '~/layouts/ref/RefSubLayout'
interface ICommonFunc {
id: string
title: string
slug: string
product: string
libs: string
items: ICommonFunc[]
}
interface IRefFunctionSection {
funcData: any
commonFuncData: ICommonFunc
spec: any
}
export type Flag = {
id: string
name: string
description: string
default_value: string
accepted_values: AcceptedValue[]
required?: boolean
}
export type AcceptedValue = {
id: string
name: string
type: 'string' | 'boolean' | 'object'
description?: string
}
export type Command = {
id: string
title: string
description: string
flags?: Flag[]
summary: string
tags?: []
links?: []
subcommands?: []
usage?: string
}
const CliCommandSection = (props) => {
const command = spec.commands.find((x: any) => x.id === props.funcData.id)
return (
<RefSubLayout.Section
key={command.id}
slug={command.id}
title={'$ ' + command.title}
id={command.id}
monoFont={true}
scrollSpyHeader={true}
>
<RefSubLayout.Details>
<div className="grid ref-container" id={command.id}>
<div className="border-b pb-8" key={command.id}>
<header
className={[
// 'border-b sticky top-16 z-10',
' mb-16',
].join(' ')}
>
<p className="capitalize mb-4 scroll-mt-16 mt-0 text-scale-1100 text-base">
{command.summary}
</p>
</header>
{/* {command.usage && (
<CodeBlock language="bash" className="relative">
{command.usage}
</CodeBlock>
)} */}
{command.subcommands.length > 0 && (
<div className="mb-3">
<h3 className="text-lg text-scale-1200 mb-3">Available Commands</h3>
<ul>
{command.subcommands.map((subcommand) => (
<li key={subcommand} className="flex items-center gap-3">
<div className="text-scale-900">
<IconChevronRight size={14} strokeWidth={2} />
</div>
<a
href={`#${subcommand}`}
className="transition text-scale-1100 hover:text-brand-900"
>
$ {subcommand.replace(/-/g, ' ')}
</a>
</li>
))}
</ul>
</div>
)}
{command.flags.length > 0 && (
<>
<h3 className="text-lg text-scale-1200 mb-3">Flags</h3>
<ul className="">
{command.flags.map((flag: Flag) => (
<>
<li className="mt-0">
<Param {...flag} isOptional={!flag.required}>
{flag?.accepted_values && (
<Options>
{flag?.accepted_values.map((value) => {
return <Options.Option {...value} />
})}
</Options>
)}
</Param>
</li>
</>
))}
</ul>
</>
)}
</div>
</div>
</RefSubLayout.Details>
<RefSubLayout.Examples>
{command.usage && (
<CodeBlock language="bash" className="relative">
{command.usage}
</CodeBlock>
)}
</RefSubLayout.Examples>
</RefSubLayout.Section>
)
}
export default CliCommandSection
@@ -0,0 +1,44 @@
import * as Accordion from '@radix-ui/react-accordion'
import React from 'react'
import { IconChevronRight } from '~/../../packages/ui'
const RefDetailCollapse: React.FC<{ id: string; label: string; defaultOpen?: boolean }> = ({
defaultOpen = true,
...props
}) => {
return (
<Accordion.Root
className="AccordionRoot"
type="single"
defaultValue={defaultOpen ? `${props.id}` : ''}
collapsible
>
<Accordion.Item className="AccordionItem" value={`${props.id}`}>
<Accordion.Trigger asChild>
<button
className={[
'transition-all ease-out',
'h-8',
'bg-scale-200 data-open:bg-scale-300',
'dark:bg-scale-300 dark:data-open:bg-scale-500',
'border border-scale-500 w-full flex items-center gap-3 px-5',
'rounded-tl rounded-tr',
'data-closed:rounded-bl data-closed:rounded-br',
'text-scale-1100 text-xs',
].join(' ')}
>
<div className="data-open-parent:rotate-90 text-scale-900">
<IconChevronRight size={12} strokeWidth={2} />
</div>
{props.label}
</button>
</Accordion.Trigger>
<Accordion.Content className="transition data-open:animate-slide-down data-closed:animate-slide-up">
{props.children}
</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
)
}
export default RefDetailCollapse
@@ -0,0 +1,27 @@
import RefSubLayout from '~/layouts/ref/RefSubLayout'
import { MDXRemote } from 'next-mdx-remote'
import components from '~/components'
const RefEducationSection = (props) => {
// gracefully reject pages we can't render
if (!props.markdownContent) {
//console.log(props.item.id)
return <div></div>
}
return (
<RefSubLayout.EducationSection
key={props.item.id}
title={props.item.title}
hideTitle={props.markdownContent.meta.hideTitle}
id={props.item.id}
slug={props.item.id}
scrollSpyHeader={true}
icon={props.markdownContent.meta.icon}
>
<MDXRemote {...props.markdownContent.content} components={components} />
</RefSubLayout.EducationSection>
)
}
export default RefEducationSection
@@ -0,0 +1,234 @@
import ReactMarkdown from 'react-markdown'
import { IconDatabase, Tabs } from 'ui'
import CodeBlock from '~/components/CodeBlock/CodeBlock'
import Options from '~/components/Options'
import Param from '~/components/Params'
import RefSubLayout from '~/layouts/ref/RefSubLayout'
import { extractTsDocNode, generateParameters } from '~/lib/refGenerator/helpers'
import RefDetailCollapse from '~/components/reference/RefDetailCollapse'
interface ICommonFunc {
id: string
title: string
slug: string
product: string
libs: string
items: ICommonFunc[]
}
interface IRefFunctionSection {
funcData: any
commonFuncData: ICommonFunc
spec: any
typeSpec?: any
}
const RefFunctionSection: React.FC<IRefFunctionSection> = (props) => {
const item = props.spec.functions.find((x: any) => x.id === props.funcData.id)
// gracefully return nothing if function does not exist
if (!item) return <></>
if (item && !item['$ref']) {
//console.warn('🚩 issue with $ref in:', item.id)
}
// console.log(item)
const hasTsRef = item['$ref'] || null
const tsDefinition =
hasTsRef && props.typeSpec ? extractTsDocNode(hasTsRef, props.typeSpec) : null
const parameters = hasTsRef && tsDefinition ? generateParameters(tsDefinition) : ''
const shortText = hasTsRef && tsDefinition ? tsDefinition.signatures[0].comment.shortText : ''
return (
<>
<RefSubLayout.Section
key={item.id}
title={`${props.commonFuncData.title}`}
id={item.id}
slug={props.commonFuncData.slug}
scrollSpyHeader={true}
>
<RefSubLayout.Details>
<>
<header className={['prose'].join(' ')}>
{shortText && <ReactMarkdown className="text-sm">{shortText}</ReactMarkdown>}
</header>
{item.description && (
<div className="prose">
<ReactMarkdown className="text-sm">{item.description}</ReactMarkdown>
</div>
)}
{/* {functionMarkdownContent && (
<div className="prose">
<MDXRemote {...functionMarkdownContent} components={components} />
</div>
)} */}
{item.notes && (
<div className="prose">
<ReactMarkdown className="text-sm">{item.notes}</ReactMarkdown>
</div>
)}
{/* // parameters */}
{parameters && (
<div className="not-prose mt-12">
<h5 className="mb-3 text-base text-scale-1200">Parameters</h5>
<ul className="">
{parameters.map((param) => {
// grab override params from yaml file
const overrideParams = item.overrideParams
// params from the yaml file can override the params from parameters if it matches the name
const overide = overrideParams?.filter((x) => {
return param.name === x.name
})
const paramItem = overide?.length > 0 ? overide[0] : param
return (
<Param {...paramItem}>
{paramItem.subContent && (
<div className="mt-3">
<Options>
{param.subContent.map((param) => {
return (
<>
<Options.Option {...param}>
{param.subContent && (
<Options>
{param.subContent.map((param) => {
return <Options.Option {...param} />
})}
</Options>
)}
</Options.Option>
</>
)
})}
</Options>
</div>
)}
</Param>
)
})}
</ul>
</div>
)}
</>
</RefSubLayout.Details>
<RefSubLayout.Examples>
{item.examples && (
<>
<div className="overflow-hidden w-full">
<Tabs
defaultActiveId={item.examples[0].id}
size="tiny"
type="rounded-pills"
scrollable
>
{item.examples &&
item.examples.map((example, exampleIndex) => {
const exampleString = ''
// `
// import { createClient } from '@supabase/supabase-js'
// // Create a single supabase client for interacting with your database
// const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key')
// `
const currentExampleId = example.id
const staticExample = item.examples[exampleIndex]
const response = staticExample.response
const sql = staticExample?.data?.sql
const tables = staticExample?.data?.tables
return (
<Tabs.Panel
id={example.id}
label={example.name}
className="flex flex-col gap-3"
>
{((tables && tables.length > 0) || sql) && (
<RefDetailCollapse
id={`${example.id}-${exampleIndex}-data`}
label="Example data source"
defaultOpen={false}
>
<>
{tables &&
tables.length > 0 &&
tables.map((table) => {
return (
<div className="bg-scale-300 border rounded prose max-w-none">
<div className="bg-scale-200 px-5 py-2">
<div className="flex gap-2 items-center">
<div className="text-brand-900">
<IconDatabase size={16} />
</div>
<h5 className="text-xs text-scale-1200">
{table.name}
</h5>
</div>
</div>
</div>
)
})}
{sql && (
<CodeBlock
className="useless-code-block-class my-0 border border-t-0 border-scale-500 !rounded-tl-none !rounded-tr-none"
language="sql"
hideLineNumbers={true}
>
{sql.replace(/sql/g, '').replace(/```/g, '')}
</CodeBlock>
)}
</>
</RefDetailCollapse>
)}
<CodeBlock
className="useless-code-block-class"
language="js"
hideLineNumbers={true}
>
{exampleString +
(example.code &&
example.code
.replace(/```/g, '')
.replace('js', '')
.replace('ts', ''))}
</CodeBlock>
{response && (
<RefDetailCollapse
id={`${example.id}-${exampleIndex}-response`}
label="Example response"
defaultOpen={false}
>
<CodeBlock
className="useless-code-block-class"
language="js"
hideLineNumbers={true}
>
{response.replace(/```/g, '').replace('json', '')}
</CodeBlock>
</RefDetailCollapse>
)}
</Tabs.Panel>
)
})}
</Tabs>
</div>
</>
)}
</RefSubLayout.Examples>
</RefSubLayout.Section>
</>
)
}
export default RefFunctionSection
@@ -0,0 +1,16 @@
import React from 'react'
import RefSubLayout from '~/layouts/ref/RefSubLayout'
interface Props {}
const RefHeaderSection: React.FC<Props> = (props) => {
return (
<>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>{props.children}</RefSubLayout.Details>
</RefSubLayout.EducationRow>
</>
)
}
export default RefHeaderSection
@@ -0,0 +1,88 @@
import { MDXRemote } from 'next-mdx-remote'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import components from '~/components'
import RefEducationSection from '~/components/reference/RefEducationSection'
import RefFunctionSection from '~/components/reference/RefFunctionSection'
import OldLayout from '~/layouts/Default'
import RefSubLayout from '~/layouts/ref/RefSubLayout'
import ApiOperationSection from './ApiOperationSection'
import CliCommandSection from './CLICommandSection'
interface Props {
sections: any[] // to do
spec: any // to do
typeSpec: any // to do
pageProps: any // to do, from staticProps
type: 'client-lib' | 'cli' | 'api'
}
const RefSectionHandler = (props) => {
const router = useRouter()
const slug = router.query.slug[0]
const isNewDocs = process.env.NEXT_PUBLIC_NEW_DOCS === 'true'
// When user lands on a url like http://supabase.com/docs/reference/javascript/sign-up
// find the #sign-up element and scroll to that
useEffect(() => {
if (isNewDocs && document && slug !== 'start') {
document.querySelector(`#${slug}`) && document.querySelector(`#${slug}`).scrollIntoView()
}
})
/*
* handle old ref pages
*/
if (!isNewDocs) {
return (
// @ts-ignore
<OldLayout meta={props.pageProps.meta} toc={props.pageProps.toc}>
<MDXRemote {...props.pageProps.content} components={components} />
</OldLayout>
)
}
return (
<RefSubLayout>
{props.sections.map((x) => {
switch (x.type) {
case 'markdown':
const markdownData = props.pageProps.docs.find((doc) => doc.id === x.id)
return <RefEducationSection item={x} markdownContent={markdownData} />
break
case 'function':
return (
<RefFunctionSection
funcData={x}
commonFuncData={x}
spec={props.spec}
typeSpec={props.typeSpec}
/>
)
case 'cli-command':
return <CliCommandSection funcData={x} commonFuncData={x} />
break
case 'operation':
return <ApiOperationSection funcData={x} commonFuncData={x} spec={props.spec} />
default:
return (
<RefFunctionSection
funcData={x}
commonFuncData={x}
spec={props.spec}
typeSpec={props.typeSpec}
/>
)
break
}
})}
</RefSubLayout>
)
}
export default RefSectionHandler
@@ -0,0 +1,30 @@
import RefSubLayout from '~/layouts/ref/RefSubLayout'
// @ts-expect-error
import spec from '~/../../spec/cli_v1_commands.yaml' assert { type: 'yaml' }
import Param from '~/components/Params'
import Options from '~/components/Options'
const CliGlobalFlagsHandler = () => {
return (
<RefSubLayout.EducationRow className="not-prose">
<RefSubLayout.Details>
<h3 className="text-lg text-scale-1200 mb-3">Flags</h3>
<ul className="">
{spec.flags.map((flag) => {
return (
<Param
{...flag}
isOptional={flag.required === undefined ? true : !flag.required}
></Param>
)
})}
</ul>
</RefSubLayout.Details>
<RefSubLayout.Examples></RefSubLayout.Examples>
</RefSubLayout.EducationRow>
)
}
export default CliGlobalFlagsHandler
+19 -37
View File
@@ -1,26 +1,8 @@
const authProviders = [
// {
// name: 'Email',
// // logo: '/img/libraries/dart-icon.svg',
// href: '/docs/guides/auth/auth-apple',
// official: true,
// supporter: 'Supabase',
// platform: true,
// selfHosted: true,
// },
// {
// name: 'Magic Links',
// // logo: '/img/libraries/dart-icon.svg',
// href: '/docs/guides/auth/auth-apple',
// official: true,
// supporter: 'Supabase',
// platform: true,
// selfHosted: true,
// },
{
name: 'Apple',
// logo: '/img/libraries/dart-icon.svg',
href: '/guides/auth/auth-apple',
href: '/guides/auth/social-login/auth-apple',
official: true,
supporter: 'Supabase',
platform: true,
@@ -29,7 +11,7 @@ const authProviders = [
{
name: 'Azure',
// logo: '/img/libraries/dart-icon.svg',
href: '/guides/auth/auth-azure',
href: '/guides/auth/social-login/auth-azure',
official: false,
supporter: 'TBD',
platform: true,
@@ -38,7 +20,7 @@ const authProviders = [
{
name: 'Bitbucket',
// logo: '/img/libraries/dart-icon.svg',
href: '/guides/auth/auth-bitbucket',
href: '/guides/auth/social-login/auth-bitbucket',
official: true,
supporter: 'Supabase',
platform: true,
@@ -47,7 +29,7 @@ const authProviders = [
{
name: 'Discord',
// logo: '/img/libraries/dart-icon.svg',
href: '/guides/auth/auth-discord',
href: '/guides/auth/social-login/auth-discord',
official: true,
supporter: 'Supabase',
platform: true,
@@ -56,7 +38,7 @@ const authProviders = [
{
name: 'Facebook',
// logo: '/img/libraries/dart-icon.svg',
href: '/guides/auth/auth-facebook',
href: '/guides/auth/social-login/auth-facebook',
official: true,
supporter: 'Supabase',
platform: true,
@@ -65,7 +47,7 @@ const authProviders = [
{
name: 'GitHub',
// logo: '/img/libraries/dart-icon.svg',
href: '/guides/auth/auth-github',
href: '/guides/auth/social-login/auth-github',
official: true,
supporter: 'Supabase',
platform: true,
@@ -74,7 +56,7 @@ const authProviders = [
{
name: 'GitLab',
// logo: '/img/libraries/dart-icon.svg',
href: '/guides/auth/auth-gitlab',
href: '/guides/auth/social-login/auth-gitlab',
official: true,
supporter: 'Supabase',
platform: true,
@@ -83,7 +65,7 @@ const authProviders = [
{
name: 'Google',
// logo: '/img/libraries/dart-icon.svg',
href: '/guides/auth/auth-google',
href: '/guides/auth/social-login/auth-google',
official: true,
supporter: 'Supabase',
platform: true,
@@ -91,7 +73,7 @@ const authProviders = [
},
{
name: 'Keycloak',
href: '/guides/auth/auth-keycloak',
href: '/guides/auth/social-login/auth-keycloak',
official: true,
supporter: 'Supabase',
platform: true,
@@ -100,7 +82,7 @@ const authProviders = [
{
name: 'LinkedIn',
// logo: '/img/libraries/dart-icon.svg',
href: '/guides/auth/auth-linkedin',
href: '/guides/auth/social-login/auth-linkedin',
official: true,
supporter: 'Supabase',
platform: true,
@@ -109,7 +91,7 @@ const authProviders = [
{
name: 'MessageBird',
// logo: '/img/libraries/dart-icon.svg',
href: '/guides/auth/auth-messagebird',
href: '/guides/auth/phone-login/messagebird',
official: false,
supporter: 'MessageBird',
platform: true,
@@ -118,7 +100,7 @@ const authProviders = [
{
name: 'Notion',
// logo: '/img/libraries/notion-icon.svg',
href: '/guides/auth/auth-notion',
href: '/guides/auth/social-login/auth-notion',
official: true,
supporter: 'Supabase',
platform: true,
@@ -127,7 +109,7 @@ const authProviders = [
{
name: 'Slack',
// logo: '/img/libraries/dart-icon.svg',
href: '/guides/auth/auth-slack',
href: '/guides/auth/social-login/auth-slack',
official: true,
supporter: 'Supabase',
platform: true,
@@ -136,7 +118,7 @@ const authProviders = [
{
name: 'Spotify',
// logo: '/img/libraries/dart-icon.svg',
href: '/guides/auth/auth-spotify',
href: '/guides/auth/social-login/auth-spotify',
official: true,
supporter: 'Supabase',
platform: true,
@@ -145,7 +127,7 @@ const authProviders = [
{
name: 'Twitter',
// logo: '/img/libraries/dart-icon.svg',
href: '/guides/auth/auth-twitter',
href: '/guides/auth/social-login/auth-twitter',
official: true,
supporter: 'Supabase',
platform: true,
@@ -154,7 +136,7 @@ const authProviders = [
{
name: 'Twitch',
// logo: '/img/libraries/dart-icon.svg',
href: '/guides/auth/auth-twitch',
href: '/guides/auth/social-login/auth-twitch',
official: true,
supporter: 'Supabase',
platform: true,
@@ -163,7 +145,7 @@ const authProviders = [
{
name: 'Zoom',
// logo: '/img/libraries/dart-icon.svg',
href: '/guides/auth/auth-zoom',
href: '/guides/auth/social-login/auth-zoom',
official: true,
supporter: 'Supabase',
platform: true,
@@ -172,7 +154,7 @@ const authProviders = [
{
name: 'Twilio',
// logo: '/img/libraries/dart-icon.svg',
href: '/guides/auth/auth-twilio',
href: '/guides/auth/phone-login/twilio',
official: true,
supporter: 'Supabase',
platform: true,
@@ -180,7 +162,7 @@ const authProviders = [
},
{
name: 'Vonage',
href: '/guides/auth/auth-vonage',
href: '/guides/auth/phone-login/vonage',
official: false,
supporter: 'Supabase',
platform: true,
+1 -1
View File
@@ -90,7 +90,7 @@
},
{
"text": "SupaSquad",
"url": "/docs/handbook/supasquad"
"url": "/supasquad"
},
{
"text": "DevTo",
+5
View File
@@ -118,6 +118,11 @@ const Nav = [
url: '/reference/dart/storage-from-createsignedurl',
items: [],
},
{
name: 'from.createSignedUrls()',
url: '/reference/dart/storage-from-createsignedurls',
items: [],
},
{
name: 'from.getPublicUrl()',
url: '/reference/dart/storage-from-getpublicurl',
+3
View File
@@ -0,0 +1,3 @@
# testing
@mildtomato is testing a new ref layout/mdx setup
+37
View File
@@ -0,0 +1,37 @@
---
slug: /
sidebar_position: 1
id: management-api
title: Management API
sidebar_label: Management API
---
The Management API allows you to manage your projects programmatically.
## Status
The Management API is in `beta`. It is usable in it's current state, but it's likely that there will be breaking changes.
## Authentication
All API requests require a Supabase Personal token to be included in the Authorization header: `Authorization Bearer <supabase_personal_token`.
To generate or manage your API token, visit your [account](https://app.supabase.com/account/tokens) page.
Your API tokens carry the same privileges as your user account, so be sure to keep it secret.
```bash
$ curl https://api.supabase.com/v1/projects \
-H "Authorization: Bearer sbp_bdd0••••••••••••••••••••••••••••••••4f23"
```
All API requests must be authenticated and made over HTTPS.
## Rate limits
The API is currently subject to our fair-use policy. In the future, are likely to introduce rate limits.
All resources created via the API are subject to the pricing detailed on our [Pricing](https://supabase.com/pricing) pages.
## Additional links
- [OpenAPI Docs](https://api.supabase.com/api/v1)
- [OpenAPI Spec](https://api.supabase.com/api/v1-json)
- Reporting bugs and issues: [github.com/supabase/supabase](https://github.com/supabase/supabase)
+53
View File
@@ -0,0 +1,53 @@
---
id: introduction
title: introduction
hideTitle: true
---
<div className="flex items-start gap-6 not-prose" id="introduction">
<img src="/docs/img/icons/menu/reference-api.svg" className="w-8 h-8 rounded" />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-scale-1200 m-0">Management API</h1>
</div>
</div>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
Manage your Supabase organizations and projects programmatically.
## Status
The Management API is in `beta`. It is usable in it's current state, but it's likely that there will be breaking changes.
## Authentication
All API requests require a Supabase Personal token to be included in the Authorization header: `Authorization Bearer <supabase_personal_token`.
To generate or manage your API token, visit your [account](https://app.supabase.com/account/tokens) page.
Your API tokens carry the same privileges as your user account, so be sure to keep it secret.
```bash
curl https://api.supabase.com/v1/projects \
-H "Authorization: Bearer sbp_bdd0••••••••••••••••••••••••••••••••4f23"
```
All API requests must be authenticated and made over HTTPS.
## Rate limits
The API is currently subject to our fair-use policy. In the future, are likely to introduce rate limits.
All resources created via the API are subject to the pricing detailed on our [Pricing](https://supabase.com/pricing) pages.
</RefSubLayout.Details>
<RefSubLayout.Examples>
Additional links
- [OpenAPI Docs](https://api.supabase.com/api/v1)
- [OpenAPI Spec](https://api.supabase.com/api/v1-json)
- [Report bugs and issues](https://github.com/supabase/supabase)
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
+8
View File
@@ -0,0 +1,8 @@
---
title: Global Flags
description: 'Supabase CLI supports global flags for every command.'
---
Supabase CLI supports global flags for every command.
<CliGlobalFlagsHandler />
+43
View File
@@ -0,0 +1,43 @@
---
id: introduction
title: Introduction
hideTitle: true
---
<div className="flex items-start gap-6 not-prose" id="introduction">
<img src="/docs/img/icons/menu/reference-cli.svg" className="w-8 h-8 rounded" />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-scale-1200 m-0">Supabase CLI</h1>
</div>
</div>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.
The CLI is still under development, but it contains all the functionality for working with your Supabase projects and the Supabase Platform.
- Run Supabase locally: [`supabase start`](/docs/reference/cli/usage#supabase-start)
- Manage database migrations: [`supabase migration`](/docs/reference/cli/usage#supabase-migration)
- CI/CD for releasing to production: [`supabase db push`](/docs/reference/cli/usage#supabase-db-push)
- Manage your Supabase projects: [`supabase projects`](/docs/reference/cli/usage#supabase-projects)
- Generate types directly from your database schema: [`supabase gen types`](/docs/reference/cli/usage#supabase-gen)
- A [community-supported GitHub Action](https://github.com/lyqht/generate-supabase-db-types-github-action) to generate TypeScript types
- Shell autocomplete: [`supabase completion`](/docs/reference/cli/usage#supabase-completion)
- A [community-supported Fig autocomplete spec](https://fig.io/manual/supabase) for macOS terminal
</RefSubLayout.Details>
<RefSubLayout.Examples>
### Additional links
- [Install the Supabase CLI](/docs/guides/cli)
- [Source code](https://github.com/supabase/cli)
- [Known bugs and issues](https://github.com/supabase/cli/issues)
- [Supabase CLI v1 and Management API Beta](https://supabase.com/blog/supabase-cli-v1-and-admin-api-beta)
- [Video: Announcing CLI V1 and Management API Beta](https://www.youtube.com/watch?v=OpPOaJI_Z28)
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
+50
View File
@@ -0,0 +1,50 @@
---
id: initializing
title: 'Initializing'
slug: initializing
---
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
Initialize Supabase with the static `initialize()` method on `Supabase` class.
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="flutter"
>
<TabPanel id="flutter" label="Flutter">
```dart main.dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(
url: 'https://xyzcompany.supabase.co',
anonKey: 'public-anon-key',
);
runApp(MyApp());
}
```
</TabPanel>
<TabPanel id="dart" label="Other Dart Project">
```dart
final supabase = SupabaseClient(
'https://xyzcompany.supabase.co',
'public-anon-key',
);
```
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
+41
View File
@@ -0,0 +1,41 @@
---
id: installing
title: 'Installing'
slug: installing
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supabase.yml
---
### Install from pub.dev
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
You can install Supabase package from [pub.dev](https://pub.dev/packages/supabase_flutter)
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="flutter"
>
<TabPanel id="flutter" label="Flutter">
```sh Terminal
flutter pub add supabase_flutter
```
</TabPanel>
<TabPanel id="dart" label="Other Dart Project">
```sh Terminal
dart pub add supabase
```
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
+23
View File
@@ -0,0 +1,23 @@
---
id: introduction
title: Introduction
hideTitle: true
---
<div className="flex items-start gap-6 not-prose" id="introduction">
<img src="/docs/img/icons/menu/reference-dart.svg" className="w-8 h-8 rounded" />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-scale-1200 m-0">Flutter Client Library</h1>
<h2 className="text-base font-mono text-scale-1100">supabase-flutter</h2>
</div>
</div>
<div className="max-w-xl">
This reference documents every object and method available in Supabase's Flutter
library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can
use supabase-flutter to interact with your Postgres database, listen to database changes, invoke
Deno Edge Functions, build login and user management functionality, and manage large files.
<br />
<br />
We also provide [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
</div>
@@ -0,0 +1,6 @@
---
id: release-notes
title: Release Notes
---
## dart this is the release notes file.
@@ -0,0 +1,6 @@
---
id: release-notes
title: Release Notes
---
## dart v0 this is the release notes file.
+12
View File
@@ -0,0 +1,12 @@
---
id: auth
title: 'Auth'
slug: auth
icon: /docs/img/icons/menu/auth
---
<RefHeaderSection>
Every Supabase project comes with a full Postgres database, a free and open source database which is considered one of the world's most stable and advanced databases.
</RefHeaderSection>
@@ -0,0 +1,12 @@
---
id: database
title: 'Database'
slug: database
icon: /docs/img/icons/menu/database
---
<RefHeaderSection>
Every Supabase project comes with a full Postgres database, a free and open source database which is considered one of the world's most stable and advanced databases.
</RefHeaderSection>
@@ -0,0 +1,79 @@
---
id: installing
title: 'Installing'
slug: installing
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supabase.yml
---
### Install as package
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
You can install @supabase/supabase-js via the terminal.
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="npm"
>
<TabPanel id="npm" label="npm">
```sh Terminal
npm install @supabase/supabase-js
```
</TabPanel>
<TabPanel id="yarn" label="Yarn">
```sh Terminal
yarn add @supabase/supabase-js
```
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
### Install via CDN
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
You can install @supabase/supabase-js via CDN links.
</RefSubLayout.Details>
<RefSubLayout.Examples>
```js
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
//or
<script src="https://unpkg.com/@supabase/supabase-js@2"></script>
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
### Use at runtime in Deno
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
You can use supabase-js in the Deno runtime via esm.sh:
</RefSubLayout.Details>
<RefSubLayout.Examples>
```ts
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
@@ -0,0 +1,20 @@
---
id: introduction
title: Introduction
hideTitle: true
---
<div className="flex items-start gap-6 not-prose" id="introduction">
<img src="/docs/img/icons/menu/reference-javascript.svg" className="w-8 h-8 rounded" />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-scale-1200 m-0">Javascript Client Library</h1>
<h2 className="text-base font-mono text-scale-1100">@supabase/supabase-js</h2>
</div>
</div>
<div className="max-w-xl">
This reference documents every object and method available in Supabase's isomorphic JavaScript
library, supabase-js. You can use supabase-js to interact with your Postgres database, listen to
database changes, invoke Deno Edge Functions, build login and user management functionality, and
manage large files.
</div>
@@ -0,0 +1,309 @@
---
id: release-notes
title: Release Notes 2.0.0
---
Supabase.js v2 release notes.
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
Install the latest version of @supabase/supabase-js
</RefSubLayout.Details>
<RefSubLayout.Examples>
```bash Terminal
npm install @supabase/supabase-js
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
### Explicit constructor options
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
All client specific options within the constructor are keyed to the library.
See [PR](https://github.com/supabase/supabase-js/pull/458):
</RefSubLayout.Details>
<RefSubLayout.Examples>
```jsx
const supabase = createClient(apiURL, apiKey, {
db: {
schema: 'public',
},
auth: {
storage: AsyncStorage,
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: true,
},
realtime: {
channels,
endpoint,
},
global: {
fetch: customFetch,
headers: DEFAULT_HEADERS,
},
})
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
### Typescript support
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
The libraries now support typescript.
</RefSubLayout.Details>
<RefSubLayout.Examples>
```ts v1.0
// previously definitions were injected in the `from()` method
supabase.from<Definitions['Message']>('messages').select('\*')
```
---
```ts v2.0
import type { Database } from './DatabaseDefinitions'
// definitions are injected in `createClient()`
const supabase = createClient<Database>(SUPABASE_URL, ANON_KEY)
const { data } = await supabase.from('messages').select().match({ id: 1 })
```
</RefSubLayout.Examples>
<RefSubLayout.Details>
Types can be generated via the CLI:
</RefSubLayout.Details>
<RefSubLayout.Examples>
```bash Terminal
supabase start
supabase gen types typescript --local > DatabaseDefinitions.ts
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
### Data operations return minimal
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
`.insert()` / `.upsert()` / `.update()` / `.delete()` don't return rows by default: [PR](https://github.com/supabase/postgrest-js/pull/276).
Previously, these methods return inserted/updated/deleted rows by default (which caused [some confusion](https://github.com/supabase/supabase/discussions/1548)), and you can opt to not return it by specifying `returning: 'minimal'`. Now the default behavior is to not return rows. To return inserted/updated/deleted rows, add a `.select()` call at the end, e.g.:
</RefSubLayout.Details>
<RefSubLayout.Examples>
```sql
const { data, error } = await supabase
.from('my_table')
.delete()
.eq('id', 1)
.select()
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
### New ordering defaults
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
`.order()` now defaults to Postgres’s default: [PR](https://github.com/supabase/postgrest-js/pull/283).
Previously `nullsFirst` defaults to `false` , meaning `null`s are ordered last. This is bad for performance if e.g. the column uses an index with `NULLS FIRST` (which is the default direction for indexes).
</RefSubLayout.Details>
<RefSubLayout.Examples>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
### Cookies and localstorage namespace
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
Storage key name in the Auth library has changed to include project reference which means that existing websites that had their JWT expiry set to a longer time could find their users logged out with this upgrade.
</RefSubLayout.Details>
<RefSubLayout.Examples>
```jsx
const defaultStorageKey = `sb-${new URL(this.authUrl).hostname.split('.')[0]}-auth-token`
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
### New Auth Types
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
Typescript typings have been reworked. `Session` interface now guarantees that it will always have an `access_token`, `refresh_token` and `user`
</RefSubLayout.Details>
<RefSubLayout.Examples>
```jsx ./types.ts
interface Session {
provider_token?: string | null
access_token: string
expires_in?: number
expires_at?: number
refresh_token: string
token_type: string
user: User
}
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
### New Auth methods
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
We're removing the `signIn()` method in favor of more explicit function signatures:
`signInWithPassword()`, `signInWithOtp()`, and `signInWithOtp()`.
</RefSubLayout.Details>
<RefSubLayout.Examples>
```ts v1.0
const { data } = await supabase.auth.signIn({
email: 'hello@example',
password: 'pass',
})
```
---
```ts v2.0
const { data } = await supabase.auth.signInWithPassword({
email: 'hello@example',
password: 'pass',
})
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
### New Realtime methods
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
There is a new `channel()` method in the Realtime library, which will be used for our Multiplayer updates.
We will deprecate the `.from().on().subscribe()` method previosuly used for listening to postgres changes.
</RefSubLayout.Details>
<RefSubLayout.Examples>
```ts
supabase
.channel('any_string_you_want')
.on('presence', { event: 'track' }, (payload) => {
console.log(payload)
})
.subscribe()
supabase
.channel('any_string_you_want')
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'movies',
},
(payload) => {
console.log(payload)
}
)
.subscribe()
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
### Deprecated setAuth()
Deprecated and removed `setAuth()` . To set a custom `access_token` jwt instead, pass the custom header into the `createClient()` method provided: ([PR](https://github.com/supabase/gotrue-js/pull/340))
### All changes
- `supabase-js`
- `shouldThrowOnError` has been removed until all the client libraries support this option ([PR](https://github.com/supabase/supabase-js/pull/490)).
- `postgrest-js`
- TypeScript typings have been reworked [PR](https://github.com/supabase/postgrest-js/pull/279)
- Use `undefined` instead of `null` for function params, types, etc. (https://github.com/supabase/postgrest-js/pull/278)
- Some features are now obsolete: (https://github.com/supabase/postgrest-js/pull/275)
- filter shorthands (e.g. `cs` vs. `contains`)
- `body` in response (vs. `data`)
- `upsert`ing through the `.insert()` method
- `auth` method on `PostgrestClient`
- client-level `throwOnError`
- `gotrue-js`
- `supabase-js` client allows passing a `storageKey` param which will allow the user to set the key used in local storage for storing the session. By default, this will be namespace-d with the supabase project ref. ([PR](https://github.com/supabase/supabase-js/pull/460))
- `signIn` method is now split into `signInWithPassword` , `signInWithOtp` , `signInWithOAuth` ([PR](https://github.com/supabase/gotrue-js/pull/304))
- Deprecated and removed `session()` , `user()` in favour of using `getSession()` instead. `getSession()` will always return a valid session if a user is already logged in, meaning no more random logouts. ([PR](https://github.com/supabase/gotrue-js/pull/299))
- Deprecated and removed setting for `multitab` support because `getSession()` and gotrue’s reuse interval setting takes care of session management across multiple tabs ([PR](https://github.com/supabase/gotrue-js/pull/366))
- No more throwing of random errors, gotrue-js v2 always returns a custom error type: ([PR](https://github.com/supabase/gotrue-js/pull/341))
- `AuthSessionMissingError`
- Indicates that a session is expected but missing
- `AuthNoCookieError`
- Indicates that a cookie is expected but missing
- `AuthInvalidCredentialsError`
- Indicates that the incorrect credentials were passed
- Renamed the `api` namespace to `admin` , the `admin` namespace will only contain methods that should only be used in a trusted server-side environment with the service role key
- Moved `resetPasswordForEmail` , `getUser` and `updateUser` to the `GoTrueClient` which means they will be accessible from the `supabase.auth` namespace in `supabase-js` instead of having to do `supabase.auth.api` to access them
- Removed `sendMobileOTP` , `sendMagicLinkEmail` in favor of `signInWithOtp`
- Removed `signInWithEmail`, `signInWithPhone` in favor of `signInWithPassword`
- Removed `signUpWithEmail` , `signUpWithPhone` in favor of `signUp`
- Replaced `update` with `updateUser`
- `storage-js`
- Return types are more strict. Functions types used to indicate that the data returned could be null even if there was no error. We now make use of union types which only mark the data as null if there is an error and vice versa. ([PR](https://github.com/supabase/storage-js/pull/60))
- The `upload` and `update` function returns the path of the object uploaded as the `path` parameter. Previously the returned value had the bucket name prepended to the path which made it harder to pass the value on to other storage-js methods since all methods take the bucket name and path separately. We also chose to call the returned value `path` instead of `Key` ([PR](https://github.com/supabase/storage-js/pull/75))
- `getPublicURL` only returns the public URL inside the data object. This keeps it consistent with our other methods of returning only within the data object. No error is returned since this method cannot does not throw an error ([PR](https://github.com/supabase/storage-js/pull/93))
- signed urls are returned as `signedUrl` instead of `signedURL` in both `createSignedUrl` and `createSignedUrls` ([PR](https://github.com/supabase/storage-js/pull/94))
- Encodes URLs returned by `createSignedUrl`, `createSignedUrls` and `getPublicUrl` ([PR](https://github.com/supabase/storage-js/pull/86))
- `createsignedUrl` used to return a url directly and and within the data object. This was inconsistent. Now we always return values only inside the data object across all methods. ([PR](https://github.com/supabase/storage-js/pull/88))
- `createBucket` returns a data object instead of the name of the bucket directly. ([PR](https://github.com/supabase/storage-js/pull/89))
- Fixed types for metadata ([PR](https://github.com/supabase/storage-js/pull/90))
- Better error types make it easier to track down what went wrong quicker.
- `SupabaseStorageClient` is no longer exported. Use `StorageClient` instead. ([PR](https://github.com/supabase/storage-js/pull/92)).
- `realtime-js`
- `RealtimeSubscription` class no longer exists and replaced by `RealtimeChannel`.
- `RealtimeClient`'s `disconnect` method now returns type of `void` . It used to return type of `Promise<{ error: Error | null; data: boolean }`.
- Removed `removeAllSubscriptions` and `removeSubscription` methods from `SupabaseClient` class.
- Removed `SupabaseRealtimeClient` class.
- Removed `SupabaseQueryBuilder` class.
- Removed `SupabaseEventTypes` type.
- Thinking about renaming this to something like `RealtimePostgresChangeEvents` and moving it to `realtime-js` v2.
- Removed `.from(’table’).on(’INSERT’, () ⇒ {}).subscribe()` in favor of new Realtime client API.
- `functions-js`
- supabase-js v1 only threw an error if the fetch call itself threw an error (network errors, etc) and not if the function returned HTTP errors like 400s or 500s. We have changed this behaviour to return an error if your function throws an error.
- We have introduced new error types to distinguish between different kinds of errors. A `FunctionsHttpError` error is returned if your function throws an error, `FunctionsRelayError` if the Supabase Relay has an error processing your function and `FunctionsFetchError` if there is a network error in calling your function.
- The correct content-type headers are automatically attached when sending the request if you don’t pass in a `Content-Type` header and pass in an argument to your function. We automatically attach the content type for `Blob`, `ArrayBuffer`, `File`, `FormData` ,`String` . If it doesn’t match any of these we assume the payload is `json` , we serialise the payload as JSON and attach the content type as `application/json`.
- `responseType` does not need to be explicitly passed in. We parse the response based on the `Content-Type` response header sent by the function. We support parsing the responses as `text`, `json`, `blob`, `form-data` and are parsed as `text` by default.
@@ -0,0 +1,128 @@
---
id: typescript-support
title: Typescript Support
---
`supabase-js` supports Typescript.
## Generating types
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
You can use our CLI to generate types:
</RefSubLayout.Details>
<RefSubLayout.Examples>
```bash Terminal
supabase start
supabase gen types typescript --local > lib/database.types.ts
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
These types are generated directly from your database.
There is a difference between `selects`, `inserts`, and `updates`, because often you will set default values in your database for specific columns.
With default values you do not need to send any data over the network, even if that column is a "required" field. Our type system is granular
enough to handle these situations.
</RefSubLayout.Details>
<RefSubLayout.Examples>
Given a table `public.movies`, the definition will provide the following data:
```ts /types.ts
interface Database {
public: {
Tables: {
movies: {
Row: {} // The data expected to be returned from a "select" statement.
Insert: {} // The data expected passed to an "insert" statement.
Update: {} // The data expected passed to an "update" statement.
}
}
}
}
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
## Injecting type definitions
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
You can enrich the supabase client with the types you generated with Supabase.
</RefSubLayout.Details>
<RefSubLayout.Examples>
```ts ./index.tsx
import { createClient } from '@supabase/supabase-js'
import { Database } from 'lib/database.types'
const supabase = createClient<Database>(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY
)
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
## Type hints
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
`supabase-js` always returns a `data` object (for success), and an `error` response (for unsuccessful requests).
This provides a simple interface to get the relevant types returned from any function:
</RefSubLayout.Details>
<RefSubLayout.Examples>
```ts
export async function getMovies() {
return await supabase.from('movies').select(`id, title`)
}
type MoviesResponse = Awaited<ReturnType<typeof getMovies>>
export type MoviesResponseSuccess = MoviesResponse['data']
export type MoviesResponseError = MoviesResponse['error']
````
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
## Nested tables
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
For advanced queries such as nested tables, you may want to construct your own types.
</RefSubLayout.Details>
<RefSubLayout.Examples>
```ts ./index.ts
import supabase from '~/lib/supabase'
import type { Database } from '~/lib/database.types'
async function getMovies() {
return await supabase.from('movies').select('id, title, actors(\*)')
}
type Actors = Database['public']['tables']['actors']['row']
type MoviesResponse = Awaited<ReturnType<typeof getMovies>>
type MoviesResponseSuccess = MoviesResponse['data'] & {
actors: Actors[]
}
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
@@ -0,0 +1,20 @@
---
id: introduction
title: Introduction
hideTitle: true
---
<div className="flex items-start gap-6 not-prose" id="introduction">
<img src="/docs/img/icons/javascript-icon.svg" className="w-8 h-8 rounded" />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-scale-1200 m-0">Javascript Client Library</h1>
<h2 className="text-base font-mono text-scale-1100">@supabase/supabase-js</h2>
</div>
</div>
<div className="max-w-xl">
This reference documents every object and method available in Supabase's isomorphic JavaScript
library, supabase-js. You can use supabase-js to interact with your Postgres database, listen to
database changes, invoke Deno Edge Functions, build login and user management functionality, and
manage large files.
</div>
@@ -0,0 +1,6 @@
---
id: release-notes
title: Release Notes
---
## js v1 this is the release notes file.
+57
View File
@@ -0,0 +1,57 @@
---
slug: /
sidebar_position: 1
id: realtime
title: Supabase Realtime Server
sidebar_label: Supabase Realtime Server
---
Supabase Realtime is a server built with Elixir using the [Phoenix Framework](https://www.phoenixframework.org) that allows you to listen to changes in your PostgreSQL database via logical replication and then broadcast those changes via WebSockets.
There are two versions of this server: `Realtime` and `Realtime RLS`.
`Realtime` server works by:
1. listening to PostgreSQL's replication functionality (using PostgreSQL's logical decoding)
2. converting the byte stream into JSON
3. broadcasting to all connected clients over WebSockets
`Realtime RLS` server works by:
1. polling PostgreSQL's replication functionality (using PostgreSQL's logical decoding and [wal2json](https://github.com/eulerto/wal2json) output plugin)
2. passing database changes to a [Write Ahead Log Realtime Unified Security (WALRUS)](https://github.com/supabase/walrus) PostgresSQL function and receiving a list of authorized subscribers depending on Row Level Security (RLS) policies
3. converting the changes into JSON
4. broadcasting to authorized subscribers over WebSockets
## Why not just use PostgreSQL's `NOTIFY`?
A few reasons:
1. You don't have to set up triggers on every table.
2. `NOTIFY` has a payload limit of 8000 bytes and will fail for anything larger. The usual solution is to send an ID and then fetch the record, but that's heavy on the database.
3. `Realtime` server consumes two connections to the database, then you can connect many clients to this server. Easier on your database, and to scale up you just add additional `Realtime` servers.
## Benefits
1. The beauty of listening to the replication functionality is that you can make changes to your database from anywhere - your API, directly in the DB, via a console, etc. - and you will still receive the changes via WebSockets.
2. Decoupling. For example, if you want to send a new slack message every time someone makes a new purchase you might build that functionality directly into your API. This allows you to decouple your async functionality from your API.
3. This is built with Phoenix, an [extremely scalable Elixir framework](https://www.phoenixframework.org/blog/the-road-to-2-million-websocket-connections).
## Does this server guarantee delivery of every data change?
Not yet! Due to the following limitations:
1. Postgres database runs out of disk space due to Write-Ahead Logging (WAL) buildup, which can crash the database and prevent Realtime server from receiving and broadcasting changes. This can be mitigated in the Realtime RLS version of this server by setting the Postgres config `max_slot_wal_keep_size` to a reasonable size.
2. Realtime server can crash due to a larger replication lag than available memory, forcing the creation of a new replication slot and resetting replication to read from the latest WAL data.
3. When Realtime server falls too far behind for any reason, for example disconnecting from database as WAL continues to build up, then database can delete WAL segments the server still needs to read from, for example after reconnecting.
## Client libraries
- [JavaScript](https://github.com/supabase/realtime-js)
- [Dart](https://github.com/supabase/realtime-dart)
## Additional Links
- [Source Code](https://github.com/supabase/realtime)
- [Known bugs and issues](https://github.com/supabase/realtime/issues)
- [Realtime Guides](https://supabase.com/docs/guides/realtime)
@@ -0,0 +1,38 @@
---
id: introduction
title: Introduction
hideTitle: true
---
<div className="flex items-start gap-6 not-prose" id="introduction">
<img src="/docs/img/icons/menu/reference-auth.svg" className="w-8 h-8 rounded" />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-scale-1200 m-0">Self-Hosting Auth</h1>
</div>
</div>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
The Supabase Auth Server (GoTrue) is a JSON Web Token (JWT)-based API for managing users and issuing access tokens.
GoTrue is an open-source API written in Golang, that acts as a self-standing API service for handling user registration and authentication for JAM projects. It's based on OAuth2 and JWT and handles user signup, authentication, and custom user data.
</RefSubLayout.Details>
<RefSubLayout.Examples>
### Client libraries
- [JavaScript](https://github.com/supabase/gotrue-js)
- [Dart](https://github.com/supabase/gotrue-dart)
### Additional links
- [Source code](https://github.com/supabase/gotrue)
- [Known bugs and issues](https://github.com/supabase/gotrue/issues)
- [Auth guides](/docs/guides/auth)
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
@@ -0,0 +1,73 @@
---
id: introduction
title: Introduction
hideTitle: true
---
<div className="flex items-start gap-6 not-prose" id="introduction">
<img src="/docs/img/icons/menu/reference-realtime.svg" className="w-8 h-8 rounded" />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-scale-1200 m-0">Self-Hosting Realtime</h1>
</div>
</div>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
Supabase Realtime is a server built with Elixir using the [Phoenix Framework](https://www.phoenixframework.org) that allows you to listen to changes in your PostgreSQL database via logical replication and then broadcast those changes via WebSockets.
There are two versions of this server: `Realtime` and `Realtime RLS`.
`Realtime` server works by:
1. Listening to PostgreSQL's replication functionality (using PostgreSQL's logical decoding)
2. Converting the byte stream into JSON
3. Broadcasting to all connected clients over WebSockets
`Realtime RLS` server works by:
1. Polling PostgreSQL's replication functionality (using PostgreSQL's logical decoding and [wal2json](https://github.com/eulerto/wal2json) output plugin)
2. Passing database changes to a [Write Ahead Log Realtime Unified Security (WALRUS)](https://github.com/supabase/walrus) PostgresSQL function and receiving a list of authorized subscribers depending on Row Level Security (RLS) policies
3. Converting the changes into JSON
4. Broadcasting to authorized subscribers over WebSockets
## Why not just use PostgreSQL's `NOTIFY`?
A few reasons:
1. You don't have to set up triggers on every table.
2. `NOTIFY` has a payload limit of 8000 bytes and will fail for anything larger. The usual solution is to send an ID and then fetch the record, but that's heavy on the database.
3. `Realtime` server consumes two connections to the database, then you can connect many clients to this server. Easier on your database, and to scale up you just add additional `Realtime` servers.
## Benefits
1. The beauty of listening to the replication functionality is that you can make changes to your database from anywhere - your API, directly in the DB, via a console, etc. - and you will still receive the changes via WebSockets.
2. Decoupling. For example, if you want to send a new slack message every time someone makes a new purchase you might build that functionality directly into your API. This allows you to decouple your async functionality from your API.
3. This is built with Phoenix, an [extremely scalable Elixir framework](https://www.phoenixframework.org/blog/the-road-to-2-million-websocket-connections).
## Does this server guarantee delivery of every data change?
Not yet! Due to the following limitations:
1. Postgres database runs out of disk space due to Write-Ahead Logging (WAL) buildup, which can crash the database and prevent Realtime server from receiving and broadcasting changes. This can be mitigated in the Realtime RLS version of this server by setting the Postgres config `max_slot_wal_keep_size` to a reasonable size.
2. Realtime server can crash due to a larger replication lag than available memory, forcing the creation of a new replication slot and resetting replication to read from the latest WAL data.
3. When Realtime server falls too far behind for any reason, for example disconnecting from database as WAL continues to build up, then database can delete WAL segments the server still needs to read from, for example after reconnecting.
</RefSubLayout.Details>
<RefSubLayout.Examples>
### Client libraries
- [JavaScript](https://github.com/supabase/realtime-js)
- [Dart](https://github.com/supabase/realtime-dart)
### Additional links
- [Source code](https://github.com/supabase/realtime)
- [Known bugs and issues](https://github.com/supabase/realtime/issues)
- [Realtime guides](/docs/guides/realtime)
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
@@ -0,0 +1,43 @@
---
id: introduction
title: Introduction
hideTitle: true
---
<div className="flex items-start gap-6 not-prose" id="introduction">
<img src="/docs/img/icons/menu/reference-storage.svg" className="w-8 h-8 rounded" />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-scale-1200 m-0">Self-Hosting Storage</h1>
</div>
</div>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
An S3 compatible object storage service that integrates with Postgres.
- Uses Postgres as it's datastore for storing metadata
- Authorization rules are written as Postgres Row Level Security policies
- Integrates with S3 as the storage backend (with more in the pipeline!)
- Extremely lightweight and performant
</RefSubLayout.Details>
<RefSubLayout.Examples>
### Client libraries
- [JavaScript](https://github.com/supabase/storage-js)
- [Dart](https://github.com/supabase/storage-dart)
### Additional links
- [Source code](https://github.com/supabase/storage-api)
- [Known bugs and issues](https://github.com/supabase/storage-js/issues)
- [Storage guides](/docs/guides/storage)
- [OpenAPI docs](https://supabase.github.io/storage-api/)
- [Why we built a new object storage service](https://supabase.com/blog/supabase-storage)
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
@@ -28,6 +28,33 @@ Once you initialize Supabase in your `main()` method, you can access the `Supaba
final supabase = Supabase.instance.client;
```
### Call `initialize()` with custom headers
You can pass `headers` to initialize your Supabase client with customer headers.
Here is an example of passing a custom auth header to Supabase client.
```dart title=main.dart
Future<void> main() async {
await Supabase.initialize(
url: 'https://xyzcompany.supabase.co',
anonKey: 'public-anon-key',
headers: {
'Authorization': 'Bearer $accessToken',
},
);
runApp(const MyApp());
}
```
### Call `initialize()`multiple times
You can initialize Supabase multiple times in your app. When doing so, call `dispose()` each time before you initialize again.
```dart
Supabase.instance.dispose();
await Supabase.initialize(url: 'https://xyzcompany.supabase.co', anonKey: 'public-anon-key');
```
## Other Dart Projects
You can initialize a new Supabase client using the `SupabaseClient()` method.
@@ -5,9 +5,6 @@ slug: installing
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supabase.yml
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
## JavaScript
<Tabs
@@ -16,7 +16,7 @@ supabase gen types typescript --local > lib/database.types.ts
These types are generated directly from your database. Given a table `public.movies`, the definition will provide the following data:
```ts
```tsx hello.ts
interface Database {
public: {
Tables: {
@@ -16,7 +16,7 @@ and is the easiest way to interact with everything we offer within the Supabase
## Parameters
<ul className="method-list-group">
<li className="method-list-item">
<h4 className="method-list-item-label">
<span className="method-list-item-label-name">
@@ -34,7 +34,7 @@ and is the easiest way to interact with everything we offer within the Supabase
The unique Supabase URL which is supplied when you create a new project in your project dashboard.
</div>
</li>
<li className="method-list-item">
@@ -54,7 +54,7 @@ The unique Supabase URL which is supplied when you create a new project in your
The unique Supabase Key which is supplied when you create a new project in your project dashboard.
</div>
</li>
<li className="method-list-item">
@@ -74,7 +74,7 @@ The unique Supabase Key which is supplied when you create a new project in your
No description provided.
</div>
</li>
</ul>
@@ -117,7 +117,7 @@ const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key
```
By default the API server points to the `public` schema. You can enable other database schemas within the Dashboard.
Go to `Settings > API > Schema` and add the schema which you want to expose to the API.
Go to `Settings > API > Schema` and add the schema which you want to expose to the API. You also need to grant `USAGE` on your new schema with the grants you desire, such as `SELECT, INSERT, UPDATE, DELETE`.
Note: each client connection can only access a single schema, so the code above can access the `other_schema` schema but cannot access the `public` schema.
+1 -1
View File
@@ -33,7 +33,7 @@ export default async function gen(inputFileName: string, outputDir: string, apiU
type v3OperationWithPath = OpenAPIV3.OperationObject & {
path: string
}
type enrichedOperation = OpenAPIV3.OperationObject & {
export type enrichedOperation = OpenAPIV3.OperationObject & {
path: string
fullPath: string
operationId: string
+16 -7
View File
@@ -16,6 +16,13 @@ import { TsDoc, OpenRef } from './legacy/definitions'
import { uniqBy } from 'lodash'
import * as fs from 'fs'
import * as yaml from 'js-yaml'
import { flattenSections } from '../lib/helpers'
const commonDocSpecJson = JSON.parse(
fs.readFileSync('../../spec/common-client-libs-sections.json', 'utf8')
)
const flattenedCommonDocSpecJson = flattenSections(commonDocSpecJson)
export default async function gen(inputFileName: string, outputDir: string) {
const docSpec = yaml.load(fs.readFileSync(inputFileName, 'utf8'))
@@ -24,7 +31,7 @@ export default async function gen(inputFileName: string, outputDir: string) {
const definition = JSON.parse(defRef)
const id = docSpec.info.id
const allLanguages = docSpec.info.libraries
const pages = Object.entries(docSpec.pages).map(([name, x]: [string, OpenRef.Page]) => ({
const pages = Object.entries(docSpec.functions).map(([name, x]: [string, OpenRef.Page]) => ({
...x,
pageName: name,
}))
@@ -38,7 +45,9 @@ export default async function gen(inputFileName: string, outputDir: string) {
// Generate Pages
pages.forEach(async (pageSpec: OpenRef.Page) => {
try {
const slug = slugify(pageSpec.pageName)
// get the slug from common-client-libs.yml
const slug = flattenedCommonDocSpecJson.find((item) => item.id === pageSpec.id).slug
const hasTsRef = pageSpec['$ref'] || null
const tsDefinition = hasTsRef && extractTsDocNode(hasTsRef, definition)
if (hasTsRef && !tsDefinition) throw new Error('Definition not found: ' + hasTsRef)
@@ -48,8 +57,8 @@ export default async function gen(inputFileName: string, outputDir: string) {
// Create page
const content = Page({
slug: (docSpec.info.slugPrefix || '') + slug,
id: slug,
slug: slug,
id: pageSpec.id,
specFileName: docSpec.info.specUrl || inputFileName,
title: pageSpec.title || pageSpec.pageName,
description,
@@ -58,7 +67,7 @@ export default async function gen(inputFileName: string, outputDir: string) {
examples: generateExamples(id, pageSpec['examples'] || [], allLanguages),
notes: pageSpec.notes,
})
//console.log({ slug })
// Write to disk
const dest = outputDir + `/${slug}.mdx`
await writeToDisk(dest, content)
@@ -146,7 +155,7 @@ const mergeUnion = (paramDefinition: TsDoc.TypeDefinition) => {
}
const methodListGroup = (items) => `
<ul className="method-list-group">
<ul className="method-list-group not-prose">
${items}
</ul>
`
@@ -265,7 +274,7 @@ function extractParamTypeAsString(paramDefinition) {
* Iterates through the definition to find the correct definition.
* You can pass it a deeply nested node using dot notation. eg: 'LoggedInUser.data.email'
*/
function extractTsDocNode(nodeToFind: string, definition: any) {
export function extractTsDocNode(nodeToFind: string, definition: any) {
const nodePath = nodeToFind.split('.')
let i = 0
let previousNode = definition
@@ -28,6 +28,7 @@ export namespace OpenRef {
title?: string
description?: string
notes?: string
id: string
// $ref?: string // reference to a TSDoc node
}
}
+2 -2
View File
@@ -35,7 +35,7 @@ toc_max_heading_level: 3
<% if(operation.parameters && operation.parameters.filter((parameter) => parameter.in === 'path').length > 0){ %>
#### Path Parameters
<ul className="method-list-group">
<ul className="method-list-group not-prose">
<% operation.parameters
.filter((parameter) => parameter.in === 'path').forEach(function(parameter){ %>
<li className="method-list-item">
@@ -68,7 +68,7 @@ toc_max_heading_level: 3
<% if(operation.parameters && operation.parameters.filter((parameter) => parameter.in === 'header').length > 0){ %>
#### Header Parameters
<ul className="method-list-group">
<ul className="method-list-group not-prose">
<% operation.parameters
.filter((parameter) => parameter.in === 'header').forEach(function(parameter){ %>
<li className="method-list-item">
+6 -6
View File
@@ -6,19 +6,19 @@ title: Usage
toc_max_heading_level: 3
---
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY -->
{/* <!-- AUTOGENERATED: DO NOT EDIT DIRECTLY --> */}
<%- info.description %>
<% commands.forEach(function(command){ %>
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY -->
{/* <!-- AUTOGENERATED: DO NOT EDIT DIRECTLY --> */}
<br /><br />
<%- command.heading %>
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY -->
{/* <!-- AUTOGENERATED: DO NOT EDIT DIRECTLY --> */}
<%- command.description %>
@@ -27,7 +27,7 @@ toc_max_heading_level: 3
<%- command.usage %>
<% }; %>
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY -->
{/* <!-- AUTOGENERATED: DO NOT EDIT DIRECTLY --> */}
<% if(command.subcommands.length > 0){ %>
**Available Commands**
@@ -38,7 +38,7 @@ toc_max_heading_level: 3
<% } %>
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY -->
{/* <!-- AUTOGENERATED: DO NOT EDIT DIRECTLY --> */}
**Options**
@@ -46,7 +46,7 @@ toc_max_heading_level: 3
<% }); %>
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY -->
{/* <!-- AUTOGENERATED: DO NOT EDIT DIRECTLY --> */}
`.trim()
export default template
@@ -22,7 +22,7 @@ toc_max_heading_level: 3
<%- parameter.description %>
<ul class="method-list-group">
<ul class="method-list-group not-prose">
<li class="method-list-item-label flex items-center gap-4">Required: <code><%= parameter.required %></code></li>
<li class="method-list-item-label flex items-center gap-4">Default: <code><%- parameter?.default ? parameter?.default : 'None' %></code></li><% if(parameter?.links?.length){ %>
<li>
+29
View File
@@ -0,0 +1,29 @@
import { useState, useCallback, useEffect } from 'react'
const useHash = () => {
const [hash, setHash] = useState(() =>
typeof window !== 'undefined' ? window.location.hash.split('#')[1] : undefined
)
const hashChangeHandler = useCallback(() => {
setHash(window.location.hash.split('#')[1])
}, [])
useEffect(() => {
window.addEventListener('hashchange', hashChangeHandler)
return () => {
window.removeEventListener('hashchange', hashChangeHandler)
}
}, [])
const updateHash = useCallback(
(newHash) => {
if (newHash !== hash) window.location.hash = newHash
},
[hash]
)
return [hash, updateHash]
}
export default useHash
+12
View File
@@ -0,0 +1,12 @@
import { useState, useEffect } from 'react'
const useIsMounted = (): boolean => {
const [isMounted, setIsMounted] = useState(false)
useEffect(() => {
setIsMounted(() => true)
}, [])
return isMounted
}
export default useIsMounted
+30
View File
@@ -0,0 +1,30 @@
import { proxy, useSnapshot } from 'valtio'
export const menuState = proxy({
// values
menuActiveRefId: 'home',
// set states
setMenuActiveRefId: (value) => {
menuState.menuActiveRefId = value
},
menuLevelId: '',
setMenuLevelId: (value) => {
menuState.menuMobileOpen = false
menuState.menuLevelId = value
},
menuMobileOpen: false,
setMenuMobileOpen: (value) => {
menuState.menuMobileOpen = value
},
})
export const useMenuActiveRefId = () => {
return useSnapshot(menuState).menuActiveRefId
}
export const useMenuLevelId = () => {
return useSnapshot(menuState).menuLevelId
}
export const useMenuMobileOpen = () => {
return useSnapshot(menuState).menuMobileOpen
}
+37
View File
@@ -0,0 +1,37 @@
import useIsMounted from './useIsMounted'
import { useEffect, useState } from 'react'
const useWindowLocation = (): Location | void => {
const isMounted = useIsMounted()
const [location, setLocation] = useState<Location | void>(isMounted ? window.location : undefined)
const setWindowLocation = (location) => {
setLocation(location)
}
useEffect(() => {
if (!isMounted) return
if (!location) {
setWindowLocation(window.location)
}
const handler = () => {
setWindowLocation(window.location)
}
window.addEventListener('popstate', handler)
return () => {
window.removeEventListener('popstate', handler)
}
}, [isMounted])
useEffect(() => {
// console.log('location, in hook useffect', location)
}, [location])
return location
}
export default useWindowLocation
+13
View File
@@ -0,0 +1,13 @@
import sections from '../../../../spec/common-api-sections.json' assert { type: 'json' }
import { flattenSections } from '../helpers.mjs'
const flatSections = flattenSections(sections)
export function generateAPIPages() {
let apiPages = []
flatSections.map((section) => {
apiPages.push(`reference/api/${section.slug}`)
})
return apiPages
}
+18
View File
@@ -0,0 +1,18 @@
import fs from 'fs'
import yaml from 'js-yaml'
import cliCommonSections from '../../../../spec/common-cli-sections.json' assert { type: 'json' }
import { flattenSections } from '../helpers.mjs'
const flatCLISections = flattenSections(cliCommonSections)
const cliSpec = yaml.load(fs.readFileSync(`../../spec/cli_v1_commands.yaml`, 'utf8'))
export function generateCLIPages() {
let cliPages = []
cliSpec.commands.map((section) => {
const slug = flatCLISections.find((item) => item.id === section.id)?.slug
if (slug) cliPages.push(`reference/cli/${slug}`)
})
return cliPages
}
@@ -0,0 +1,27 @@
import fs from 'fs'
import yaml from 'js-yaml'
import commonLibSections from '../../../../spec/common-client-libs-sections.json' assert { type: 'json' }
import { flattenSections } from '../helpers.mjs'
const flatCommonLibSections = flattenSections(commonLibSections)
const clientLibFiles = [
{ fileName: 'supabase_js_v2', label: 'javascript', version: 'v2', versionSlug: false },
{ fileName: 'supabase_js_v1', label: 'javascript', version: 'v1', versionSlug: true },
{ fileName: 'supabase_dart_v1', label: 'dart', version: 'v1', versionSlug: false },
{ fileName: 'supabase_dart_v0', label: 'dart', version: 'v0', versionSlug: true },
{ fileName: 'supabase_dart_v0', label: 'dart', version: 'v0', versionSlug: true },
]
export function generateReferencePages() {
let refPages = []
clientLibFiles.map((file) => {
const spec = yaml.load(fs.readFileSync(`../../spec/${file.fileName}.yml`, 'utf8'))
spec.functions.map((fn) => {
const slug = flatCommonLibSections.find((item) => item.id === fn.id)?.slug
refPages.push(`reference/${file.label}/${file.versionSlug ? file.version + '/' : ''}${slug}`)
})
})
return refPages
}
+23 -37
View File
@@ -1,65 +1,49 @@
import { writeFileSync } from 'fs'
import { globby } from 'globby'
import prettier from 'prettier'
/*
* kudos to leerob from vercel
* https://leerob.io/blog/nextjs-sitemap-robots
*/
import { writeFileSync } from 'fs'
import { globby } from 'globby'
import prettier from 'prettier'
import { generateCLIPages } from './files/cli.mjs'
import { generateReferencePages } from './files/reference-lib.mjs'
import { generateAPIPages } from './files/api.mjs'
const referencePages = generateReferencePages()
const cliPages = generateCLIPages()
const apiPages = generateAPIPages()
async function generate() {
const prettierConfig = await prettier.resolveConfig('./.prettierrc.js')
const rawPages = await globby([
// guides
'docs/*.mdx',
'!docs/404.mdx',
'pages/**/*.mdx',
// reference
'docs/reference/*.mdx',
'docs/reference/javascript/*.mdx',
'docs/reference/javascript/generated/*.mdx',
'!docs/reference/javascript/v1.mdx', // ignore this
'docs/reference/dart/*.mdx',
'docs/reference/dart/generated/*.mdx',
'!docs/reference/dart/v0.mdx', // ignore this
'docs/reference/api/*.mdx',
'docs/reference/api/generated/*.mdx',
'docs/reference/cli/*.mdx',
'docs/reference/cli/generated/*.mdx',
// misc reference
'docs/reference/postgres/*.mdx',
'docs/reference/postgres/generated/*.mdx',
'docs/reference/realtime/*.mdx',
'docs/reference/realtime/generated/*.mdx',
'docs/reference/storage/*.mdx',
'docs/reference/storage/generated/*.mdx',
'docs/reference/auth/*.mdx',
'docs/reference/auth/generated/*.mdx',
'!pages/404.mdx',
'!pages/404.tsx',
'!pages/ref-pages.mdx',
])
const pages = rawPages.map((x) => {
const guidePages = rawPages.map((x) => {
let string = x
string = string.replace('/generated', '')
string = string.replace('pages/', '')
string = string.replace('.mdx', '')
return string
})
// add static OSS page
pages.unshift('docs/oss')
// add static homepage
pages.unshift('docs')
// combine the guidePages with the ref pages
const allPages = guidePages.concat(referencePages, cliPages, apiPages)
const sitemap = `
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${pages
${allPages
.map((path) => {
// fix homepage
path = path.replace('pages/index', '')
return `
<url>
<loc>${`https://supabase.com/${path.replace('pages/', 'docs/')}`}</loc>
<loc>${`https://supabase.com/docs/${path}`}</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
@@ -75,7 +59,9 @@ async function generate() {
})
const sitemapFilePath = `public/sitemap.xml`
console.log(`Total of ${pages.length} pages in sitemap, located at /apps/docs/${sitemapFilePath}`)
console.log(
`Total of ${allPages.length} pages in sitemap, located at /apps/docs/${sitemapFilePath}`
)
// eslint-disable-next-line no-sync
writeFileSync(sitemapFilePath, formatted)
+15
View File
@@ -0,0 +1,15 @@
export function flattenSections(sections) {
var a = []
for (var i = 0; i < sections.length; i++) {
if (sections[i].id) {
// only push a section that has an id
// these are reserved for sidebar subtitles
a.push(sections[i])
}
if (sections[i].items) {
// if there are subitems, loop through
a = a.concat(flattenSections(sections[i].items))
}
}
return a
}
+15
View File
@@ -0,0 +1,15 @@
// generator files need their own tsconfig.json because they don't like "module": "esnext" setting that nextjs requires in the main tsconfig
{
"compilerOptions": {
"incremental": true,
"noImplicitAny": false,
"esModuleInterop": true,
"resolveJsonModule": true,
"baseUrl": ".",
"paths": {
"~/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
+15 -6
View File
@@ -4,16 +4,21 @@ import NavBar from '../components/Navigation/NavBar'
import SideBar from '../components/Navigation/SideBar'
import Footer from '../components/Footer'
import TableOfContents from '~/components/TableOfContents'
import { menuItems } from '~/components/Navigation/Navigation.constants'
import { useRouter } from 'next/router'
import { getPageType } from '~/lib/helpers'
interface Props {
meta: { title: string; description?: string; hide_table_of_contents?: boolean }
children: any
toc?: any
menuItems: any
currentPage: string
menuItems?: any
currentPage?: string
}
const Layout: FC<Props> = ({ meta, children, toc, menuItems, currentPage }) => {
const Layout: FC<Props> = ({ meta, children, toc }) => {
const { asPath } = useRouter()
useEffect(() => {
const key = localStorage.getItem('supabaseDarkMode')
if (!key) {
@@ -27,6 +32,10 @@ const Layout: FC<Props> = ({ meta, children, toc, menuItems, currentPage }) => {
const hasTableOfContents =
toc !== undefined && toc.json.filter((item) => item.lvl !== 1 && item.lvl <= 3).length > 0
const pageType = getPageType(asPath)
console.log('Default, pageType', pageType)
return (
<>
<NextSeo
@@ -35,7 +44,7 @@ const Layout: FC<Props> = ({ meta, children, toc, menuItems, currentPage }) => {
openGraph={{
title: meta?.title,
description: meta?.description,
url: `https://supabase.com/docs/${currentPage}`,
url: `https://supabase.com/docs/${asPath}`,
images: [
{
url: `https://supabase.com/docs/img/supabase-og-image.png`,
@@ -45,9 +54,9 @@ const Layout: FC<Props> = ({ meta, children, toc, menuItems, currentPage }) => {
/>
<main>
<NavBar currentPage={currentPage} />
<NavBar />
<div className="flex w-full flex-row">
<SideBar menuItems={menuItems} />
<SideBar menuItems={menuItems[pageType]} />
<div className="main-content-pane docs-width grid md:grid-cols-12 gap-4 justify-between p-4 pb-8 w-full">
<div
className={`${
+6 -103
View File
@@ -1,112 +1,15 @@
import { MDXProvider } from '@mdx-js/react'
import { useEffect, FC, useRef, useState } from 'react'
import { NextSeo } from 'next-seo'
import NavBar from '../components/Navigation/NavBar'
import SideBar from '../components/Navigation/SideBar'
import Footer from '../components/Footer'
import GuidesTableOfContents from '~/components/GuidesTableOfContents'
import { useRouter } from 'next/router'
import { getPageType } from '../lib/helpers'
import components from '~/components'
import { menuItems } from '../components/Navigation/Navigation.constants'
import { FC } from 'react'
import GuideLayout from './guides'
interface Props {
meta: { title: string; description?: string; hide_table_of_contents?: boolean }
meta: { title: string; description?: string; hide_table_of_contents?: boolean; video?: string }
children: any
toc?: any
currentPage: string
currentPage?: string
}
const Layout: FC<Props> = ({ meta, children }) => {
const { asPath } = useRouter()
const page = getPageType(asPath)
useEffect(() => {
const key = localStorage.getItem('supabaseDarkMode')
if (!key) {
// Default to dark mode if no preference config
document.documentElement.className = 'dark'
} else {
document.documentElement.className = key === 'true' ? 'dark' : ''
}
}, [])
const articleRef = useRef()
const [tocList, setTocList] = useState([])
useEffect(() => {
const articleEl = articleRef.current as HTMLElement
if (!articleRef.current) return
const headings = Array.from(articleEl.querySelectorAll('h2, h3'))
const newHeadings = headings
.filter((heading) => heading.id)
.map((heading) => {
const text = heading.textContent.replace('#', '')
const link = heading.querySelector('a').getAttribute('href')
const level = heading.tagName === 'H2' ? 2 : 3
return { text, link, level }
})
setTocList(newHeadings)
}, [])
const hasTableOfContents = tocList.length > 0
return (
<>
<NextSeo
title={`${meta?.title} | Supabase`}
description={meta?.description ? meta?.description : meta?.title}
openGraph={{
title: meta?.title,
description: meta?.description,
url: `https://supabase.com/docs${asPath}`,
images: [
{
url: `https://supabase.com/docs/img/supabase-og-image.png`,
},
],
}}
/>
<main>
<NavBar currentPage={page} />
<div className="flex w-full flex-row">
<SideBar menuItems={menuItems[page]} />
<div className="main-content-pane docs-width grid md:grid-cols-12 gap-4 justify-between p-4 pb-8 w-full">
<div
className={`${
meta?.hide_table_of_contents || !hasTableOfContents
? 'col-span-12 xl:col-start-2 xl:col-span-10 2xl:col-start-3 2xl:col-span-8'
: 'col-span-12 lg:col-span-9'
} py-2 lg:py-4 px-2 lg:px-8 mx-auto`}
>
<article
ref={articleRef}
className={`${
meta?.hide_table_of_contents || !hasTableOfContents ? 'xl:min-w-[880px]' : ''
} doc-content-container prose dark:prose-dark dark:bg-scale-200 width-full mt-8 2xl:max-w-[880px]`}
>
{meta?.title && <h1>{meta.title}</h1>}
<MDXProvider components={components}>{children}</MDXProvider>
</article>
</div>
{hasTableOfContents && !meta?.hide_table_of_contents && (
<div
className={[
'border-scale-400 dark:bg-scale-200 table-of-contents-height border-l',
'thin-scrollbar overflow-y-auto sticky hidden xl:block md:col-span-3 px-2',
].join(' ')}
>
<GuidesTableOfContents list={tocList} />
</div>
)}
</div>
</div>
<Footer />
</main>
</>
)
const Layout: FC<Props> = (props) => {
return GuideLayout(props)
}
export default Layout
Loaded 100 of 1065 files, more files were not shown because too many files have changed in this diff. Show more