diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5d9f46f73b2..57849f5e09a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,4 @@ /studio/ @supabase/frontend /apps/www/ @supabase/frontend -/apps/reference/ @supabase/docs +/apps/docs/ @supabase/docs /spec/ @supabase/docs diff --git a/.github/workflows/docs-compilation-check.yml b/.github/workflows/docs-compilation-check.yml deleted file mode 100644 index 0c41676a3a6..00000000000 --- a/.github/workflows/docs-compilation-check.yml +++ /dev/null @@ -1,42 +0,0 @@ -# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions - -name: Docs Compilation Check - -on: - pull_request: - branches: [master] - paths: - - 'apps/reference/**' - -jobs: - build: - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [16.x] - # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ - - steps: - - uses: actions/checkout@v2 - - # Ref: https://github.com/actions/setup-node/issues/214 - - name: Reconfigure git to use HTTP authentication - run: > - git config --global url."https://github.com/".insteadOf - ssh://git@github.com/ - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node-version }} - cache: 'npm' - - - name: Install Deps - run: npm ci - working-directory: ./apps/reference - - - name: Build - run: npm run build - working-directory: ./apps/reference diff --git a/.github/workflows/fix-typos.yml b/.github/workflows/fix-typos.yml index faa0e01c84d..cf114552968 100644 --- a/.github/workflows/fix-typos.yml +++ b/.github/workflows/fix-typos.yml @@ -12,7 +12,7 @@ jobs: with: ref: master - uses: sobolevn/misspell-fixer-action@master - - uses: peter-evans/create-pull-request@v2.4.4 + - uses: peter-evans/create-pull-request@v4 env: ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true' with: diff --git a/.github/workflows/publish_image.yml b/.github/workflows/publish_image.yml index 8ed1db87295..6106dad6b42 100644 --- a/.github/workflows/publish_image.yml +++ b/.github/workflows/publish_image.yml @@ -2,6 +2,8 @@ name: Publish to Image Registry on: push: + branches: + - 'studio' tags: - '*' workflow_dispatch: @@ -25,6 +27,7 @@ jobs: 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' }} - uses: docker/setup-qemu-action@v2 @@ -49,7 +52,8 @@ jobs: - uses: docker/build-push-action@v3 with: push: true - context: '{{defaultContext}}:studio' + context: '{{defaultContext}}' + file: studio/Dockerfile target: production platforms: linux/amd64,linux/arm64 tags: ${{ steps.meta.outputs.tags }} diff --git a/DEVELOPERS.md b/DEVELOPERS.md index 5a1476143f1..585e7359e3d 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -1,45 +1,47 @@ # Developing Supabase -1. [Development setup](#development-setup) - - [Install dependencies](#install-dependencies) - - [Fork the repository](#fork-the-repository) -1. [Build Supabase](#build-supabase) - - [Choose a directory](#choose-a-directory) -1. [Start a development server](#start-a-development-server) - - [Supabase Website](#supabase-website) - - [Supabase Docs](#supabase-docs) - - [Supabase Studio](#supabase-studio) -1. [Create a pull request](#create-a-pull-request) +1. [Getting started](#getting-started) + - [Install dependencies](#install-dependencies) +2. [Local development](#local-development) + - [Fork the repository](#fork-the-repository) + - [Clone the repo](#clone-the-repo) + - [Running turborepo](#running-turborepo) + - [Shared components](#shared-components) + - [Installing packages](#installing-packages) +3. [Create a pull request](#create-a-pull-request) - [Common tasks](#common-tasks) - [Add a redirect](#add-a-redirect) -- [Monorepo](#monorepo) - - [Getting started](#getting-started) - - [Shared components](#shared-components) - - [Installing packages](#installing-packages) - - [Development](#development) - [Community channels](#community-channels) -## Development setup +## Getting started -Thanks for your interest in Supabase and for wanting to contribute! Before you begin, read the +Thanks for your interest in [Supabase](https://supabase.com) and for wanting to contribute! Before you begin, read the [code of conduct](https://github.com/supabase/.github/blob/main/CODE_OF_CONDUCT.md) and check out the [existing issues](https://github.com/supabase/supabase/issues). -This document describes how to set up your development environment to build and test Supabase. +This document describes how to set up your development environment to build and test [Supabase](https://supabase.com). ### Install dependencies -You need to install and configure the following dependencies on your machine to build Supabase: +You need to install and configure the following dependencies on your machine to build [Supabase](https://supabase.com): - [Git](http://git-scm.com/) - [Node.js v16.x (LTS)](http://nodejs.org) - [npm](https://www.npmjs.com/) version 7+ or [Yarn](https://yarnpkg.com/) +## Local development + +We are in the process of migrating this repository to monorepo, using [Turborepo](https://turborepo.org/docs). + +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, you must fork the [Supabase Repository](https://github.com/supabase/supabase). +To contribute code to [Supabase](https://supabase.com), you must fork the [Supabase Repository](https://github.com/supabase/supabase). -## Build Supabase +### Clone the repo 1. Clone your GitHub forked repository: @@ -52,104 +54,61 @@ To contribute code to Supabase, you must fork the [Supabase Repository](https:// cd supabase ``` -### Choose a directory +### Running turborepo -Choose if you want to work on the [Supabase Website](https://supabase.com), [Supabase Docs](https://supabase.com/docs), or [Supabase Studio](https://app.supabase.com). +[Supabase](https://supabase.com) uses [Turborepo](https://turborepo.org/docs) to manage and run this monorepo. -1. Go to the [Supabase Website](https://supabase.com) directory +1. Install the dependences in the root of the repo. ```sh - cd apps/www + npm install # install dependencies ``` - Go to the [Supabase Docs](https://supabase.com/docs) directory - +2. You can then run the apps simultaneously with the following. ```sh - cd apps/reference + npm run dev # start all the applications ``` - Go to the [Supabase Studio](https://app.supabase.com) directory +Then visit, and edit, any of the following sites: - ```sh - cd studio - ``` +| 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 | -1. Install npm/yarn dependencies: +#### Running sites individually - npm +You can run any of the sites individually by using the scope name. For example: - ```sh - npm install - ``` +```sh +npm run dev:www +``` - or with yarn +#### Shared components - ```sh - yarn install - ``` +The monorepo has a set of shared components under `/packages`: -## Start a development server +- `/packages/common`: Common React code, shared between all sites. +- `/packages/config`: All shared config +- `/packages/spec`: Generates documentation using spec files. +- `/packages/tsconfig`: Shared Typescript settings -To debug code and to see your changes in real time, it is often useful to have a local HTTP server. Click one of the three links below to choose which development server you want to start. +#### Installing packages -- [Supabase Website](#supabase-website) -- [Supabase Docs](#supabase-docs) -- [Supabase Studio](#supabase-studio) +Installing a package with NPM workspaces requires you to add the `-w` flag to tell NPM which workspace you want to install into. Do not install dependencies in their local folder, install them from the route using the `-w` flag. -### Supabase Website +The format is: `npm install -w=`. -The website is moving to a new monorepo setup. See the [Monorepo](#monorepo) section below. +For example: -### Supabase Docs +- `npm install react -w common`: installs into `./packages/common` +- `npm install react -w www`: installs into `./apps/www` +- `npm install react -w studio`: installs into `./studio` -1. Build development server +You do not need to install `devDependencies` in each workspace. These can all be installed in the root package. - npm - - ```sh - npm run build - ``` - - or with yarn - - ```sh - yarn build - ``` - -1. Start development server - - npm - - ```sh - npm run start - ``` - - or with yarn - - ```sh - yarn start - ``` - -1. Access the local server in your web browser at http://localhost:3010/docs. - -### Supabase Studio - -1. Start development server - - npm - - ```sh - npm run dev - ``` - - or with yarn - - ```sh - yarn dev - ``` - -1. Access the local server in your web browser at http://localhost:8082/. -See the [Supabase Studio readme](./studio/README.md) for more information. +--- ## Create a pull request @@ -165,63 +124,6 @@ Did you have an issue, like a merge conflict, or don't know how to open a pull r Create a new entry in the [`redirects.js`](https://github.com/supabase/supabase/blob/master/apps/www/lib/redirects.js) file in our main site. -## Monorepo - -We are in the process of migrating this repository to monorepo, using Turborepo. -Eventually, the docs and the Studio will be run using Turborepo, which will significantly improve the developer workflow. -You must be using NPM 7 or higher. - -### Getting started - -```sh -npm install # install dependencies -npm run dev # start all the applications -``` - -Then edit and visit any of the following sites: - -Site | Directory | Description | Local development server ----- | --------- | ----------- | ------------------------ -[supabase.com](https://supabase.com) | `/apps/www` | The main website | http://localhost:3000 -[supabase.com/docs](https://supabase.com/docs) | `apps/reference` | Guides and Reference documentation | http://localhost:3010/docs -[POC] Community forum | `/apps/temp-community-forum` | GitHub Discussions in a Next.js site | http://localhost:3002 -[POC] DEV articles site | `/apps/temp-community-tutorials` | A Next.js site for our DEV articles (which community members can write) | http://localhost:3003 - -### Shared components - -The monorepo has a set of shared components under `/packages`: - -- `/packages/common`: Common React code, shared between all sites. -- `/packages/config`: All shared config -- `/packages/spec`: Generates documentation using spec files. -- `/packages/tsconfig`: Shared Typescript settings -- `/packages/ui`: Shared UI components (formerly @supabase/ui) - -To use these 'packages', or any of their components from a Next.JS app, you must use `next-transpile-modules` in the `next.config.js` file. This looks like: - -```tsx -// next.config.js -const withTM = require('next-transpile-modules')(['ui', 'common']) -module.exports = withTM({}) -``` - -### Installing packages - -Installing a package with NPM workspaces requires you to add the `-w` flag to tell NPM which workspace you want to install into. - -The format is: `npm install -w=`. - -For example: - -- `npm install @supabase/ui -w common`: installs into `./packages/common` -- `npm install @supabase/ui -w www`: installs into `./apps/www` - -You do not need to install `devDependencies` in each workspace. These can all be installed in the root package. - -### Development - -`npm run dev` - --- ## Community channels diff --git a/README.md b/README.md index 711b0cf3b2c..c24e86943fa 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,15 @@ Our approach for client libraries is modular. Each sub-library is a standalone i realtime-js storage-js functions-js + + + Flutter + supabase-flutter + postgrest-dart + gotrue-dart + realtime-dart + storage-dart + functions-dart 💚 Community 💚 @@ -118,20 +127,11 @@ Our approach for client libraries is modular. Each sub-library is a standalone i storage-csharp functions-csharp - - Flutter - supabase-flutter - postgrest-dart - gotrue-dart - realtime-dart - storage-dart - functions-dart - Go - postgrest-go - - + gotrue-go - storage-go functions-go @@ -147,12 +147,12 @@ Our approach for client libraries is modular. Each sub-library is a standalone i Kotlin - - + supabase-kt postgrest-kt gotrue-kt - - - - - - + realtime-kt + storage-kt + functions-kt Python @@ -188,7 +188,16 @@ Our approach for client libraries is modular. Each sub-library is a standalone i gotrue-swift realtime-swift storage-swift - - + functions-swift + + + Godot Engine (GDScript) + supabase-gdscript + postgrest-gdscript + gotrue-gdscript + realtime-gdscript + storage-gdscript + functions-gdscript diff --git a/apps/temp-docs/.eslintrc.js b/apps/docs/.eslintrc.js similarity index 100% rename from apps/temp-docs/.eslintrc.js rename to apps/docs/.eslintrc.js diff --git a/apps/temp-docs/.gitignore b/apps/docs/.gitignore similarity index 100% rename from apps/temp-docs/.gitignore rename to apps/docs/.gitignore diff --git a/apps/reference/README.md b/apps/docs/README.md similarity index 92% rename from apps/reference/README.md rename to apps/docs/README.md index 4f3b8c0389f..7ae88ade7d0 100644 --- a/apps/reference/README.md +++ b/apps/docs/README.md @@ -42,4 +42,4 @@ We use documentation specifications which can be used to generate human-readable - CLISpec (custom to Supabase): for CLI commands and usage. The benefit of using custom specifications is that we can generate many other types from a strict schema (eg, HTML and manpages). -It also means that we can switch any documentation system we want. On this site we use Docusaurus, but in Supabase's official website we use a custom React site and expose only a subset of the available API for each tool. +It also means that we can switch any documentation system we want. On this site we use Next.JS, but in Supabase's official website we use a custom React site and expose only a subset of the available API for each tool. diff --git a/apps/docs/components/Admonition.tsx b/apps/docs/components/Admonition.tsx new file mode 100644 index 00000000000..42f7049f864 --- /dev/null +++ b/apps/docs/components/Admonition.tsx @@ -0,0 +1,51 @@ +import { FC } from 'react' +import { IconInfo, IconHelpCircle, IconAlertTriangle } from 'ui' + +interface Props { + type: 'note' | 'tip' | 'info' | 'caution' | 'danger' + label?: string + children: any +} + +const Admonition: FC = ({ type = 'note', label, children }) => { + return ( +
+
+ {type === 'note' ? ( + + ) : type === 'info' ? ( + + ) : type === 'tip' ? ( + + ) : type === 'caution' ? ( + + ) : type === 'danger' ? ( + + ) : ( + + )} +

{label || type}

+
+
{children}
+
+ ) +} + +export default Admonition diff --git a/apps/docs/components/AuthProviders.tsx b/apps/docs/components/AuthProviders.tsx new file mode 100644 index 00000000000..a1903636904 --- /dev/null +++ b/apps/docs/components/AuthProviders.tsx @@ -0,0 +1,53 @@ +import providers from '../data/authProviders' +import ButtonCard from './ButtonCard' + +export default function AuthProviders() { + return ( +
+ {providers.map((x) => ( +
+ +
+
+

{x.name}

+

+ {x.official ? ( + Official + ) : ( + Unofficial + )} +

+
+
+
+ Platform: + {x.platform.toString()} +
+
+ Self-Hosted: + {x.selfHosted.toString()} +
+
+
+
+
+ ))} +
+ ) +} diff --git a/apps/docs/components/ButtonCard.tsx b/apps/docs/components/ButtonCard.tsx new file mode 100644 index 00000000000..7d7f7e12809 --- /dev/null +++ b/apps/docs/components/ButtonCard.tsx @@ -0,0 +1,55 @@ +import React, { FC, ReactNode } from 'react' +import Link from 'next/link' +import Image from 'next/image' + +interface Props { + title: string + description?: string + to: string + icon?: string | any + children?: any + layout?: 'vertical' | 'horizontal' +} + +const ButtonCard: FC = ({ + children = undefined, + icon = undefined, + title, + description = '', + to, + layout = 'vertical', +}) => { + return ( + + + {children ? ( + children + ) : ( +
+ {icon && typeof icon == 'string' ? ( +
+ {title} +
+ ) : ( + icon + )} +

{title}

+

{description}

+
+ )} +
+ + ) +} + +export default ButtonCard diff --git a/apps/docs/components/CodeBlock/CodeBlock.tsx b/apps/docs/components/CodeBlock/CodeBlock.tsx new file mode 100644 index 00000000000..1ca6b128c12 --- /dev/null +++ b/apps/docs/components/CodeBlock/CodeBlock.tsx @@ -0,0 +1,137 @@ +import { FC } from 'react' +import CopyToClipboard from 'react-copy-to-clipboard' +import { Light as SyntaxHighlighter } from 'react-syntax-highlighter' +import monokaiCustomTheme from './CodeBlock.utils' +import { Button, IconCheck, IconCopy } from 'ui' + +import js from 'react-syntax-highlighter/dist/cjs/languages/hljs/javascript' +import ts from 'react-syntax-highlighter/dist/cjs/languages/hljs/typescript' +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 { useState } from 'react' +import { useTheme } from '../Providers' + +interface Props { + title?: string + language: 'js' | 'jsx' | 'sql' | 'py' | 'bash' | 'ts' | 'dart' + linesToHighlight?: number[] + hideCopy?: boolean + hideLineNumbers?: boolean + className?: string + value?: string + children?: string +} + +const CodeBlock: FC = ({ + title, + language, + linesToHighlight = [], + className, + value, + children, + hideCopy = false, + hideLineNumbers = false, +}) => { + const { isDarkMode } = useTheme() + const monokaiTheme = monokaiCustomTheme(isDarkMode) + + const [copied, setCopied] = useState(false) + + const handleCopy = () => { + setCopied(true) + setTimeout(() => { + setCopied(false) + }, 1000) + } + + let lang = language ? language : className ? className.replace('language-', '') : 'js' + // force jsx to be js highlighted + if (lang === 'jsx') lang = 'js' + SyntaxHighlighter.registerLanguage('js', js) + SyntaxHighlighter.registerLanguage('ts', ts) + SyntaxHighlighter.registerLanguage('py', py) + SyntaxHighlighter.registerLanguage('sql', sql) + SyntaxHighlighter.registerLanguage('bash', bash) + SyntaxHighlighter.registerLanguage('dart', dart) + + const large = false + // don't show line numbers if bash == lang + const showLineNumbers = hideLineNumbers || lang !== 'bash' + + return ( +
+ {title && ( +
+ {title.replace(/%20/g, ' ')} +
+ )} + {className ? ( + { + 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()} + + ) : ( + {value || children} + )} + {!hideCopy && (value || children) && className ? ( +
+ + + +
+ ) : null} +
+ ) +} + +export default CodeBlock diff --git a/apps/temp-docs/components/CodeBlock/CodeBlock.utils.js b/apps/docs/components/CodeBlock/CodeBlock.utils.js similarity index 72% rename from apps/temp-docs/components/CodeBlock/CodeBlock.utils.js rename to apps/docs/components/CodeBlock/CodeBlock.utils.js index e4e6aba3e26..48b62cffc1e 100644 --- a/apps/temp-docs/components/CodeBlock/CodeBlock.utils.js +++ b/apps/docs/components/CodeBlock/CodeBlock.utils.js @@ -1,3 +1,32 @@ +// https://github.com/euank/node-parse-numeric-range/blob/master/index.js +export function parseNumericRange(string) { + let res = [] + let m + + for (let str of string.split(',').map((str) => str.trim())) { + // just a number + if (/^-?\d+$/.test(str)) { + res.push(parseInt(str, 10)) + } else if ((m = str.match(/^(-?\d+)(-|\.\.\.?|\u2025|\u2026|\u22EF)(-?\d+)$/))) { + // 1-5 or 1..5 (equivalent) or 1...5 (doesn't include 5) + let [_, lhs, sep, rhs] = m + + if (lhs && rhs) { + lhs = parseInt(lhs) + rhs = parseInt(rhs) + const incr = lhs < rhs ? 1 : -1 + + // Make it inclusive by moving the right 'stop-point' away by one. + if (sep === '-' || sep === '..' || sep === '\u2025') rhs += incr + + for (let i = lhs; i !== rhs; i += incr) res.push(i) + } + } + } + + return res +} + const monokaiCustomTheme = (isDarkMode) => { return { hljs: { diff --git a/apps/docs/components/CustomHTMLElements/CustomHTMLElements.utils.ts b/apps/docs/components/CustomHTMLElements/CustomHTMLElements.utils.ts new file mode 100644 index 00000000000..305ac4fa170 --- /dev/null +++ b/apps/docs/components/CustomHTMLElements/CustomHTMLElements.utils.ts @@ -0,0 +1,79 @@ +// Check if heading has custom anchor first, before forming the anchor based on the title +export const getAnchor = (text: any): string | undefined => { + if (typeof text === 'object') { + if (Array.isArray(text)) { + const customAnchor = text.find( + (x) => typeof x === 'string' && x.includes('[#') && x.endsWith(']') + ) + if (customAnchor !== undefined) return customAnchor.slice(2, customAnchor.indexOf(']')) + + const formattedText = text + .map((x) => { + if (typeof x !== 'string') return x.props.children + else return x.trim() + }) + .map((x) => { + if (typeof x !== 'string') return x + else + return x + .toLowerCase() + .replace(/[^a-z0-9- ]/g, '') + .replace(/[ ]/g, '-') + }) + + return formattedText.join('-').toLowerCase() + } else { + const anchor = text.props.children + if (typeof anchor === 'string') { + return anchor + .toLowerCase() + .replace(/[^a-z0-9- ]/g, '') + .replace(/[ ]/g, '-') + } + return anchor + } + } else if (typeof text === 'string') { + if (text.includes('[#') && text.endsWith(']')) { + return text.slice(text.indexOf('[#') + 2, text.indexOf(']')) + } else { + return text + .toLowerCase() + .replace(/[^a-z0-9- ]/g, '') + .replace(/[ ]/g, '-') + } + } else { + return undefined + } +} + +export const removeAnchor = (text: any) => { + if (typeof text === 'object' && Array.isArray(text)) { + return text.filter((x) => !(typeof x === 'string' && x.includes('[#') && x.endsWith(']'))) + } else if (typeof text === 'string') { + if (text.indexOf('[#') > 0) return text.slice(0, text.indexOf('[#')) + else return text + } + return text +} + +export const highlightSelectedTocItem = (id: string) => { + const tocMenuItems = document.querySelectorAll('.toc-menu a') + + // find any currently active items and remove them + const currentActiveItem = document.querySelector('.toc-menu .toc__menu-item--active') + currentActiveItem?.classList.remove('toc__menu-item--active') + + // Add active class to the current item + tocMenuItems.forEach((item) => { + // @ts-ignore + if (item.href.split('#')[1] === id) { + item.classList.add('toc__menu-item--active') + } + }) +} + +// find any currently active items and remove them on route change +export const unHighlightSelectedTocItems = () => { + const currentActiveItem = document.querySelector('.toc-menu .toc__menu-item--active') + currentActiveItem?.classList.remove('toc__menu-item--active') +} diff --git a/apps/docs/components/CustomHTMLElements/Heading.tsx b/apps/docs/components/CustomHTMLElements/Heading.tsx new file mode 100644 index 00000000000..b07ea6cab17 --- /dev/null +++ b/apps/docs/components/CustomHTMLElements/Heading.tsx @@ -0,0 +1,36 @@ +import { + getAnchor, + removeAnchor, + highlightSelectedTocItem, + unHighlightSelectedTocItems, +} from './CustomHTMLElements.utils' +import { useInView } from 'react-intersection-observer' + +const Heading = ({ 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, + onChange: (inView, entry) => { + if (window.scrollY === 0) unHighlightSelectedTocItems() + if (inView) highlightSelectedTocItem(entry.target.id) + }, + }) + + return ( + + {removeAnchor(children)} + {anchor && ( + + # + + )} + + ) +} +export default Heading diff --git a/apps/docs/components/CustomHTMLElements/InlineCode.tsx b/apps/docs/components/CustomHTMLElements/InlineCode.tsx new file mode 100644 index 00000000000..95dc7603bb0 --- /dev/null +++ b/apps/docs/components/CustomHTMLElements/InlineCode.tsx @@ -0,0 +1,11 @@ +const InlineCodeTag = ({ children }) => { + // 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 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' : '' + + return {children} +} +export default InlineCodeTag diff --git a/apps/docs/components/CustomHTMLElements/index.tsx b/apps/docs/components/CustomHTMLElements/index.tsx new file mode 100644 index 00000000000..56ded7aefd1 --- /dev/null +++ b/apps/docs/components/CustomHTMLElements/index.tsx @@ -0,0 +1,3 @@ +import Heading from './Heading' + +export { Heading } diff --git a/apps/temp-docs/components/Search.tsx b/apps/docs/components/DocSearch.tsx similarity index 90% rename from apps/temp-docs/components/Search.tsx rename to apps/docs/components/DocSearch.tsx index 115d31c3245..d8bde374580 100644 --- a/apps/temp-docs/components/Search.tsx +++ b/apps/docs/components/DocSearch.tsx @@ -7,9 +7,12 @@ import { DocSearchModal } from '@docsearch/react' import clsx from 'clsx' import { useActionKey } from '~/hooks/useActionKey' -const INDEX_NAME = 'tailwindcss_dev' -const API_KEY = '5fc87cef58bb80203d2207578309fab6' -const APP_ID = 'KNPXZI5B0M' +// [Joshen] We're currently using this over Algolia search for the time being +// Refer to the comment in Navigation/AlgoliaSearch for more information about this + +const INDEX_NAME = process.env.NEXT_PUBLIC_ALGOLIA_INDEX_NAME +const API_KEY = process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY +const APP_ID = process.env.NEXT_PUBLIC_ALGOLIA_APP_ID const SearchContext = createContext(null) @@ -64,10 +67,6 @@ export function SearchProvider({ children }: any) { // @ts-ignore initialQuery={initialQuery} initialScrollY={window.scrollY} - searchParameters={{ - facetFilters: 'version:v3', - distinct: 1, - }} placeholder="Search documentation" onClose={onClose} indexName={INDEX_NAME} @@ -75,11 +74,14 @@ export function SearchProvider({ children }: any) { appId={APP_ID} // @ts-ignore navigator={{ - navigate({ suggestionUrl }) { + navigate({ itemUrl }) { setIsOpen(false) - router.push(suggestionUrl) + router.push(itemUrl) }, }} + getMissingResultsUrl={({ query }) => { + return `https://github.com/supabase/supabase/issues/new?title=Unable+to+search+docs+with+query:+${query}` + }} hitComponent={Hit} transformItems={(items) => { return items.map((item, index) => { diff --git a/apps/docs/components/Extensions.js b/apps/docs/components/Extensions.js new file mode 100644 index 00000000000..47753d6bfde --- /dev/null +++ b/apps/docs/components/Extensions.js @@ -0,0 +1,35 @@ +import React, { useState } from 'react' +import extensions from '../data/extensions.json' + +export default function Extensions() { + const [filter, setFilter] = useState('') + return ( + <> +
+ + setFilter(e.target.value)} + /> +
+
+ {extensions + .filter((x) => x.name.indexOf(filter) >= 0) + .map((extension) => ( +
+
+

+ {extension.name} +

+

+ {extension.comment.charAt(0).toUpperCase() + extension.comment.slice(1)} +

+
+
+ ))} +
+ + ) +} diff --git a/apps/docs/components/Favicons.tsx b/apps/docs/components/Favicons.tsx new file mode 100644 index 00000000000..5c65346f0fa --- /dev/null +++ b/apps/docs/components/Favicons.tsx @@ -0,0 +1,38 @@ +import Head from 'next/head' +import { useRouter } from 'next/router' + +const Favicons = () => { + const { basePath } = useRouter() + return ( + + + + + + {/* */} + + + + + + + + + ) +} + +export default Favicons diff --git a/apps/temp-docs/components/DarkModeToggle.tsx b/apps/docs/components/Footer/DarkModeToggle.tsx similarity index 100% rename from apps/temp-docs/components/DarkModeToggle.tsx rename to apps/docs/components/Footer/DarkModeToggle.tsx diff --git a/apps/temp-docs/components/Footer.tsx b/apps/docs/components/Footer/index.tsx similarity index 98% rename from apps/temp-docs/components/Footer.tsx rename to apps/docs/components/Footer/index.tsx index 0be8c2c5194..b690dd003aa 100644 --- a/apps/temp-docs/components/Footer.tsx +++ b/apps/docs/components/Footer/index.tsx @@ -1,10 +1,9 @@ -import FooterLinks from 'data/footer.json' -import SectionContainer from './SectionContainer' -import DarkModeToggle from './DarkModeToggle' -import Link from 'next/link' -import { useTheme } from './Providers' import { Badge } from 'ui' +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 * as supabaseLogoWordmarkDark from 'common/assets/images/supabase-logo-wordmark--dark.png' import * as supabaseLogoWordmarkLight from 'common/assets/images/supabase-logo-wordmark--light.png' @@ -19,7 +18,7 @@ const Footer = () => {

Footer

- +
@@ -126,7 +125,7 @@ const Footer = () => { © Supabase Inc
- +
) } diff --git a/apps/docs/components/Frameworks.tsx b/apps/docs/components/Frameworks.tsx new file mode 100644 index 00000000000..1716a2600ef --- /dev/null +++ b/apps/docs/components/Frameworks.tsx @@ -0,0 +1,106 @@ +import ButtonCard from './ButtonCard' +import { useTheme } from '~/components/Providers' + +const Frameworks = () => { + const { isDarkMode } = useTheme() + + const frameworks = [ + { + name: 'Angular', + logo: { + light: '/docs/img/libraries/angular-icon.svg', + dark: '/docs/img/libraries/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', + }, + href: '/guides/with-expo', + }, + { + name: 'Flutter', + logo: { + light: '/docs/img/libraries/flutter-icon.svg', + dark: '/docs/img/libraries/flutter-icon.svg', + }, + href: '/guides/with-flutter', + }, + { + name: 'JavaScript', + logo: { + light: '/docs/img/libraries/javascript-icon.svg', + dark: '/docs/img/libraries/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', + }, + href: '/guides/with-nextjs', + }, + { + name: 'React', + logo: { + light: '/docs/img/libraries/react-icon.svg', + dark: '/docs/img/libraries/react-icon.svg', + }, + href: '/guides/with-react', + }, + { + name: 'RedwoodJS', + logo: { + light: '/docs/img/libraries/redwoodjs-icon.svg', + dark: '/docs/img/libraries/redwoodjs-icon.svg', + }, + href: '/guides/with-redwoodjs', + }, + { + name: 'SolidJS', + logo: { + light: '/docs/img/libraries/solidjs-icon.svg', + dark: '/docs/img/libraries/solidjs-icon.svg', + }, + href: '/guides/with-solidjs', + }, + { + name: 'Svelte', + logo: { + light: '/docs/img/libraries/svelte-icon.svg', + dark: '/docs/img/libraries/svelte-icon.svg', + }, + href: '/guides/with-svelte', + }, + { + name: 'Vue', + logo: { + light: '/docs/img/libraries/vuejs-icon.svg', + dark: '/docs/img/libraries/vuejs-icon.svg', + }, + href: '/guides/with-vue-3', + }, + ] + return ( +
+ {frameworks.map((x) => ( +
+ +
+ ))} +
+ ) +} + +export default Frameworks diff --git a/apps/reference/docs/guides/functions/examples.mdx b/apps/docs/components/FunctionsExamples.tsx similarity index 56% rename from apps/reference/docs/guides/functions/examples.mdx rename to apps/docs/components/FunctionsExamples.tsx index 4c4940ba8b8..f2f603b4b98 100644 --- a/apps/reference/docs/guides/functions/examples.mdx +++ b/apps/docs/components/FunctionsExamples.tsx @@ -1,10 +1,5 @@ ---- -id: examples -title: Examples -description: Useful Supabase Edge Functions Examples. ---- +import ButtonCard from 'components/ButtonCard' -import ButtonCard from '@site/src/components/ButtonCard' const examples = [ { name: 'With supabase-js', @@ -28,36 +23,43 @@ const examples = [ }, { name: 'Building a RESTful Service API', - description: 'Learn how to use HTTP methods and paths to build a RESTful service for managing tasks.', + description: + 'Learn how to use HTTP methods and paths to build a RESTful service for managing tasks.', href: 'https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/restful-tasks/index.ts', }, { name: 'Working with Supabase Storage', - description: 'An example on reading a file from Supbase Storage.', + description: 'An example on reading a file from Supabase Storage.', 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.', + 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: 'OG Image Generation & Storage CDN Caching', + description: 'Cache generated images with Supabase Storage CDN.', + href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/og-image-with-storage-cdn', + }, + { + name: 'Get User Location', + description: `Get user location data from user's IP address.`, + href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/location', + }, ] -You can find a list of useful [Edge Function Examples](https://github.com/supabase/supabase/tree/master/examples/edge-functions) in our GitHub repository. +const FunctionsExamples = () => { + return ( +
+ {examples.map((x) => ( +
+ +
+ ))} +
+ ) +} -
-
- {examples.map((x) => ( -
- -
- ))} -
-
+export default FunctionsExamples diff --git a/apps/docs/components/GithubCard.tsx b/apps/docs/components/GithubCard.tsx new file mode 100644 index 00000000000..8780bbd0af6 --- /dev/null +++ b/apps/docs/components/GithubCard.tsx @@ -0,0 +1,17 @@ +import React from 'react' + +export default function GithubCard({ title, description, href, stars, handle }) { + return ( + +
+

{title}

+ {description} +
+
+
+
@{handle}
+
{stars} ★
+
+
+ ) +} diff --git a/apps/docs/components/GuidesTableOfContents.tsx b/apps/docs/components/GuidesTableOfContents.tsx new file mode 100644 index 00000000000..eefa3b085b1 --- /dev/null +++ b/apps/docs/components/GuidesTableOfContents.tsx @@ -0,0 +1,74 @@ +import { FC } from 'react' +import { removeAnchor } from './CustomHTMLElements/CustomHTMLElements.utils' + +const formatSlug = (slug: string) => { + // [Joshen] We will still provide support for headers declared like this: + // ## REST API {#rest-api-overview} + // At least for now, this was a docusaurus thing. + if (slug.includes('#')) return slug.split('#')[1] + return slug +} + +const formatTOCHeader = (content: string) => { + let begin = false + const res = [] + for (const x of content) { + if (x === '`') { + if (!begin) { + begin = true + res.push(``) + } else { + begin = false + res.push(``) + } + } else { + res.push(x) + } + } + return res.join('') +} + +interface TOCHeader { + id: number + level: number + text: string + link: string +} + +interface Props { + list: TOCHeader[] +} + +const GuidesTableOfContents: FC = ({ list }) => { + return ( + + + // + ) +} + +export default GuidesTableOfContents diff --git a/apps/reference/src/components/JwtGenerator.js b/apps/docs/components/JwtGenerator.js similarity index 57% rename from apps/reference/src/components/JwtGenerator.js rename to apps/docs/components/JwtGenerator.js index 7a110c5e341..46dd4758e31 100644 --- a/apps/reference/src/components/JwtGenerator.js +++ b/apps/docs/components/JwtGenerator.js @@ -1,6 +1,7 @@ import React, { useState } from 'react' import KJUR from 'jsrsasign' -import CustomCodeBlock from './CustomCodeBlock' +import CodeBlock from './CodeBlock/CodeBlock' +import { Button } from 'ui' const JWT_HEADER = { alg: 'HS256', typ: 'JWT' } const now = new Date() @@ -25,9 +26,7 @@ const serviceToken = ` `.trim() export default function JwtGenerator({}) { - const secret = [...Array(40)] - .map(() => Math.random().toString(36)[2]) - .join('') + const secret = [...Array(40)].map(() => Math.random().toString(36)[2]).join('') const [jwtSecret, setJwtSecret] = useState(secret) const [token, setToken] = useState(anonToken) @@ -45,72 +44,53 @@ export default function JwtGenerator({}) { return (
-
- +
+ setJwtSecret(e.target.value)} />
-
- -
-
- + +
+ - ) -}) -useMonaco.mockImplementation((v) => v) - -// mock usage flags -jest.mock('components/ui/Flag/Flag') -import Flag from 'components/ui/Flag/Flag' -Flag.mockImplementation(({ children }) => <>{children}) -jest.mock('hooks') -import { useFlag } from 'hooks' -useFlag.mockReturnValue(true) - -import { SWRConfig } from 'swr' -jest.mock('components/interfaces/Settings/Logs/LogsPreviewer') import LogsPreviewer from 'components/interfaces/Settings/Logs/LogsPreviewer' -LogsPreviewer.mockImplementation((props) => { - const Comp = jest.requireActual('components/interfaces/Settings/Logs/LogsPreviewer').default - // wrap with SWR to reset the cache each time - return ( - new Map(), - shouldRetryOnError: false, - }} - > - - - ) -}) - -jest.mock('hooks') import { useProjectSubscription } from 'hooks' -useProjectSubscription = jest.fn((ref) => ({ - subscription: { - tier: { - supabase_prod_id: 'tier_free', - }, - }, -})) - -import { render, fireEvent, waitFor, screen, act } from '@testing-library/react' +import { fireEvent, waitFor, screen, act } from '@testing-library/react' +import { render } from '../../helpers' import userEvent from '@testing-library/user-event' import { wait } from '@testing-library/user-event/dist/utils' import { logDataFixture } from '../../fixtures' import { LogsTableName } from 'components/interfaces/Settings/Logs' + beforeEach(() => { // reset mocks between tests get.mockReset() @@ -88,20 +36,90 @@ test.each([ queryType: 'api', tableName: undefined, tableLog: logDataFixture({ - id: 'some-id', path: 'some-path', method: 'POST', status_code: '400', metadata: undefined, }), - selectionLog: { - id: 'some-id', + selectionLog: logDataFixture({ metadata: [{ request: [{ method: 'POST' }] }], - }, + }), tableTexts: [/POST/, /some\-path/, /400/], - selectionTexts: [/POST/], + selectionTexts: [/POST/, /Timestamp/, RegExp(`${new Date().getFullYear()}.+`, 'g')], }, - // TODO: add more tests for each type of ui + { + queryType: 'database', + tableName: undefined, + tableLog: logDataFixture({ + error_severity: 'ERROR', + event_message: 'some db event', + metadata: undefined, + }), + selectionLog: logDataFixture({ + metadata: [ + { + parsed: [ + { + application_type: 'client backend', + error_severity: 'ERROR', + hint: 'some pg hint', + }, + ], + }, + ], + }), + tableTexts: [/ERROR/, /some db event/], + selectionTexts: [ + /client backend/, + /some pg hint/, + /ERROR/, + /Timestamp/, + RegExp(`${new Date().getFullYear()}.+`, 'g'), + ], + }, + { + queryType: 'auth', + tableName: undefined, + tableLog: logDataFixture({ + event_message: JSON.stringify({ + msg: 'some message', + path: '/auth-path', + level: 'info', + status: 300, + }), + }), + selectionLog: logDataFixture({ + event_message: JSON.stringify({ + msg: 'some message', + path: '/auth-path', + level: 'info', + status: 300, + }), + }), + tableTexts: [/auth\-path/, /some message/, /INFO/], + selectionTexts: [ + /auth\-path/, + /some message/, + /INFO/, + /300/, + /Timestamp/, + RegExp(`${new Date().getFullYear()}.+`, 'g'), + ], + }, + // these all use teh default selection/table renderers + ...['pgbouncer', 'postgrest', 'storage', 'realtime'].map((queryType) => ({ + queryType, + tableName: undefined, + tableLog: logDataFixture({ + event_message: 'some message', + metadata: undefined, + }), + selectionLog: logDataFixture({ + metadata: [{ some: [{ nested: 'value' }] }], + }), + tableTexts: [/some message/], + selectionTexts: [/some/, /nested/, /value/, RegExp(`${new Date().getFullYear()}.+`, 'g')], + })), ])( 'selection $queryType $tableName , can display log data and metadata', async ({ queryType, tableName, tableLog, selectionLog, tableTexts, selectionTexts }) => { diff --git a/studio/tests/pages/projects/logs-explorer/saved.test.js b/studio/tests/pages/projects/logs-explorer/saved.test.js index 6b8525aa34d..62b71eeb973 100644 --- a/studio/tests/pages/projects/logs-explorer/saved.test.js +++ b/studio/tests/pages/projects/logs-explorer/saved.test.js @@ -1,6 +1,6 @@ // mock the fetch function import { get } from 'lib/common/fetch' -import { LogsSavedPage } from 'pages/project/[ref]/logs-explorer/saved' +import { LogsSavedPage } from 'pages/project/[ref]/logs/explorer/saved' import { render } from 'tests/helpers' test('on load, refresh user content', async () => { diff --git a/studio/tests/pages/projects/logs-query.test.js b/studio/tests/pages/projects/logs-query.test.js index 562616fa426..3ae3b637d34 100644 --- a/studio/tests/pages/projects/logs-query.test.js +++ b/studio/tests/pages/projects/logs-query.test.js @@ -1,6 +1,6 @@ import { get } from 'lib/common/fetch' import { useRouter } from 'next/router' -import { LogsExplorerPage } from 'pages/project/[ref]/logs-explorer/index' +import { LogsExplorerPage } from 'pages/project/[ref]/logs/explorer/index' import { render } from 'tests/helpers' import { waitFor, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' @@ -8,7 +8,6 @@ import { logDataFixture } from '../../fixtures' import { clickDropdown } from 'tests/helpers' import dayjs from 'dayjs' import { useProjectSubscription } from 'hooks' -import UpgradePrompt from 'components/interfaces/Settings/Logs/UpgradePrompt' const defaultRouterMock = () => { const router = jest.fn() diff --git a/studio/types/base.ts b/studio/types/base.ts index 002e303ee2c..c86d136afe7 100644 --- a/studio/types/base.ts +++ b/studio/types/base.ts @@ -25,6 +25,7 @@ export interface ProjectBase { export interface Project extends ProjectBase { // available after projects.fetchDetail connectionString?: string + dbVersion?: string kpsVersion?: string restUrl?: string // store subscription tier products.metadata.supabase_prod_id diff --git a/studio/types/next.ts b/studio/types/next.ts index b525bed86bc..04bd6659eb4 100644 --- a/studio/types/next.ts +++ b/studio/types/next.ts @@ -6,6 +6,6 @@ export type AppPropsWithLayout = AppProps & { Component: NextPageWithLayout } -export type NextPageWithLayout = NextPage & { +export type NextPageWithLayout

= NextPage & { getLayout?: (page: ReactElement) => ReactNode }