transfer-out: transfer out

This commit is contained in:
alex 2023-04-25 12:01:25 +01:00
parent ec84f64e63
commit 29ed921c67
1056 changed files with 154507 additions and 0 deletions

16
.eslintignore Normal file
View file

@ -0,0 +1,16 @@
**/node_modules/*
**/out/*
**/dist/*
**/dist-cjs/*
**/dist-esm/*
**/.tsbuild*
**/.next/*
*.md
**/_archive/*
**/*.css.map
**/*.js.map
**/*.d.ts
**/*.test.ts
**/api/*
!**/pages/api/*
**/*.json

2
.eslintplugin.js Normal file
View file

@ -0,0 +1,2 @@
require('ts-node/register')
module.exports = require('./scripts/lib/eslint-plugin.ts')

86
.eslintrc.js Normal file
View file

@ -0,0 +1,86 @@
module.exports = {
extends: [
'prettier',
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@next/next/core-web-vitals',
],
plugins: ['@typescript-eslint', 'no-only-tests', 'import', 'local', '@next/next', 'react-hooks'],
settings: {
next: {
rootDir: ['apps/*/', 'packages/*/'],
},
},
ignorePatterns: ['**/*.js', '**/vscode-script-utils/*'],
rules: {
'@next/next/no-html-link-for-pages': 'off',
'react/jsx-key': 'off',
'no-non-null-assertion': 'off',
'no-fallthrough': 'off',
'@typescript-eslint/no-fallthrough': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'react/display-name': 'off',
'@next/next/no-img-element': 'off',
'@typescript-eslint/no-extra-semi': 'off',
'no-mixed-spaces-and-tabs': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
'no-throw-literal': 'error',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'error',
'import/no-extraneous-dependencies': 'error',
'import/no-internal-modules': ['error', { forbid: ['@tldraw/*/**'] }],
'@typescript-eslint/consistent-type-exports': [
'error',
{ fixMixedExportsWithInlineTypeSpecifier: true },
],
'local/no-export-star': 'error',
},
parser: '@typescript-eslint/parser',
parserOptions: {
project: true,
},
overrides: [
{
// enable the rule specifically for TypeScript files
files: ['*.ts', '*.tsx'],
rules: {
'@typescript-eslint/explicit-module-boundary-types': [0],
'no-console': ['error', { allow: ['warn', 'error'] }],
'no-only-tests/no-only-tests': ['error', { fix: true }],
},
},
{
files: ['apps/fixup/**/*', 'scripts/**/*'],
rules: {
'no-console': 'off',
},
},
{
files: ['e2e/**/*'],
rules: {
'@typescript-eslint/no-empty-function': 'off',
},
},
{
files: 'scripts/**/*',
rules: {
'import/no-extraneous-dependencies': 'off',
},
},
{
files: ['apps/examples/**/*'],
rules: {
'import/no-internal-modules': 'off',
},
},
],
}

77
.gitignore vendored Normal file
View file

@ -0,0 +1,77 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-cjs
dist-esm
.tsbuild*
.lazy
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# turborepo
.turbo
coverage
**/*.env
**/*.tsbuildinfo
**/*.css.map
**/*.js.map
apps/examples/www/index.js
apps/examples/www/index.css
nohup.out
packages/*/package
packages/*/*.tgz
tsconfig.build.json
.vercel
api-json
api-md
packages/tldraw/editor.css
packages/tldraw/ui.css
packages/assets/embed-icons
packages/assets/fonts
packages/assets/icons
packages/assets/translations
apps/examples/www/embed-icons
apps/examples/www/fonts
apps/examples/www/icons
apps/examples/www/translations
# yarn v2
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
packages/*/api
apps/examples/www/index.css
apps/examples/www/index.js
.tsbuild

11
.prettierignore Normal file
View file

@ -0,0 +1,11 @@
**/node_modules/*
**/out/*
**/dist/*
**/dist-cjs/*
**/dist-esm/*
**/api/*
!**/pages/api/*
**/.tsbuild*
**/.next/*
*.mdx
**/_archive/*

9
.prettierrc Normal file
View file

@ -0,0 +1,9 @@
{
"trailingComma": "es5",
"singleQuote": true,
"semi": false,
"printWidth": 100,
"tabWidth": 2,
"useTabs": true,
"plugins": ["prettier-plugin-organize-imports"]
}

View file

@ -0,0 +1,12 @@
diff --git a/lib/api/ExtractorConfig.js b/lib/api/ExtractorConfig.js
index a37db0d564a5662df78055ded63069a4b2706bb1..158d4b121fa0c7cf15c3a52570218a2fe67fc567 100644
--- a/lib/api/ExtractorConfig.js
+++ b/lib/api/ExtractorConfig.js
@@ -669,5 +669,5 @@ ExtractorConfig.FILENAME = 'api-extractor.json';
*/
ExtractorConfig._tsdocBaseFilePath = path.resolve(__dirname, '../../extends/tsdoc-base.json');
ExtractorConfig._defaultConfig = node_core_library_1.JsonFile.load(path.join(__dirname, '../schemas/api-extractor-defaults.json'));
-ExtractorConfig._declarationFileExtensionRegExp = /\.d\.ts$/i;
+ExtractorConfig._declarationFileExtensionRegExp = /\.d\.(m|c)?ts$/i;
//# sourceMappingURL=ExtractorConfig.js.map
\ No newline at end of file

873
.yarn/releases/yarn-3.5.0.cjs vendored Executable file

File diff suppressed because one or more lines are too long

7
.yarnrc.yml Normal file
View file

@ -0,0 +1,7 @@
enableInlineBuilds: true
lockfileFilename: public-yarn.lock
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.5.0.cjs

48
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,48 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hello@tldraw.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq

29
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,29 @@
# Contributing
Thank you for your interest in contributing to [tldraw](https://github.com/tldraw/tldraw)! We welcome any contributions to the code base and the documentation.
## Create an Issue!
Before jumping into the code, please [create an issue](https://github.com/tldraw/tldraw/issues/new/choose) to discuss your proposed changes. This will help us to make sure that your changes are aligned with the project goals and that you are not duplicating work that is already in progress.
If you are not sure whether your changes are needed, feel free to create an issue anyway and we can discuss it there. Once we have agreed on the changes, you can start working on them.
## Environment
- Ensure you have the latest version of Node and Yarn.
- Run `yarn` to install all needed dev dependencies.
## Making Changes
Pull requests are encouraged. If you want to add a feature or fix a bug:
1. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) and [clone](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository) the [repository](https://github.com/tldraw/tldraw)
2. [Create a separate branch](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/managing-branches) for your changes
3. Make your changes, and ensure that it is formatted by [Prettier](https://prettier.io) and type-checks without errors in [TypeScript](https://www.typescriptlang.org/)
4. Write tests that validate your change and/or fix.
5. Run `yarn build` and then run tests with `yarn test` (for all packages) or `yarn test:core` (for only changes to the core @tldraw/tldraw package).
6. For package changes, add docs inside the `/packages/*/README.md`. They will be copied on build to the corresponding `/docs/packages/*/index.md` file.
7. Create a changeset by running `yarn changeset`. [More info](https://github.com/atlassian/changesets).
8. Push your branch and open a PR. 🚀
Your PR will be reviewed and merged in within a day or two if everything looks good.

190
LICENSE Normal file
View file

@ -0,0 +1,190 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2023 tldraw GB Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

8
README.md Normal file
View file

@ -0,0 +1,8 @@
# tldraw-lite
<div alt style="text-align: center; transform: scale(.5);">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/tldraw/tldraw-lite/raw/main/assets/github-hero-dark.png" />
<img alt="tldraw" src="https://github.com/tldraw/tldraw-lite/raw/main/assets/github-hero-light.png" />
</picture>
</div>

1
apps/docs/.eslintrc.json Normal file
View file

@ -0,0 +1 @@
{}

40
apps/docs/.gitignore vendored Normal file
View file

@ -0,0 +1,40 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
content/gen
content.json

7
apps/docs/CHANGELOG.md Normal file
View file

@ -0,0 +1,7 @@
# @tldraw/docs
## 0.1.1-alpha.0
### Patch Changes
- Release day!

7
apps/docs/README.md Normal file
View file

@ -0,0 +1,7 @@
# tldraw-docs
<div alt style="text-align: center; transform: scale(.5);">
<picture>
<img alt="tldraw" src="https://github.com/tldraw/tldraw-lite/raw/main/docs/public/card_repo.png" />
</picture>
</div>

View file

@ -0,0 +1,27 @@
import { Article } from '@/types/content-types'
import { Icon } from './Icon'
type ArticleDetailsProps = {
article: Article
}
export function ArticleDetails({ article: { sourceUrl, date } }: ArticleDetailsProps) {
return (
<div className="article__details">
<a className="article__details__edit" href={sourceUrl}>
<Icon icon="edit" />
<span>Edit this page</span>
</a>
{date && (
<div className="article__details__timestamp">
Last edited on{' '}
{Intl.DateTimeFormat('en-gb', {
year: 'numeric',
month: 'long',
day: 'numeric',
}).format(new Date(date))}
</div>
)}
</div>
)
}

View file

@ -0,0 +1,32 @@
import { ArticleLinks } from '@/types/content-types'
import Link from 'next/link'
import { Icon } from './Icon'
type ArticleNavLinksProps = {
links: ArticleLinks
}
export function ArticleNavLinks({ links: { prev, next } }: ArticleNavLinksProps) {
return (
<div className="article__links">
{prev && (
<Link
href={`/${prev.sectionId}/${prev.categoryId}/${prev.id}`}
className="article__links__link article__links__prev"
>
<Icon icon="arrow-left" />
<span>{prev.title}</span>
</Link>
)}
{next && (
<Link
href={`/${next.sectionId}/${next.categoryId}/${next.id}`}
className="article__links__link article__links__next"
>
<span>{next.title}</span>
<Icon icon="arrow-right" />
</Link>
)}
</div>
)
}

View file

@ -0,0 +1,40 @@
import { Article, Category, Section } from '@/types/content-types'
import Link from 'next/link'
export function Breadcrumb({
section,
category,
article,
}: {
section?: Section
category?: Category
article?: Article
}) {
return (
<div className="breadcrumb">
<Link href={`/`}>tldraw</Link>
{section && (
<>
{` / `}
<Link href={`/${section.id}`}>{section.title}</Link>
{category && (
<>
{category.id === 'ucg' ? null : (
<>
{` / `}
<Link href={`/${section.id}/${category.id}`}>{category.title}</Link>
</>
)}
{article && (
<>
{` / `}
<Link href={`/${section.id}/${category.id}/${article.id}`}>{article.title}</Link>
</>
)}
</>
)}
</>
)}
</div>
)
}

View file

@ -0,0 +1,11 @@
export function Icon({ icon, className }: { icon: string; className?: string }) {
return (
<span
className={`icon ${className ?? ''}`}
style={{
mask: `url(/icons/${icon}.svg) center 100% / 100% no-repeat`,
WebkitMask: `url(/icons/${icon}.svg) center 100% / 100% no-repeat`,
}}
/>
)
}

View file

@ -0,0 +1,10 @@
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'
import { components, scope } from './mdx-components'
interface MdxProps {
mdxSource: MDXRemoteSerializeResult
}
export function Mdx({ mdxSource }: MdxProps) {
return <MDXRemote {...mdxSource} scope={scope} components={components as any} />
}

View file

@ -0,0 +1,26 @@
import Head from 'next/head'
interface MetaHeadProps {
title: string
description?: string | null
hero?: string | null
}
export function MetaHead({ title, description, hero }: MetaHeadProps) {
const TITLE = `${title} - tldraw docs`
return (
<Head>
<title>{TITLE}</title>
{description && <meta name="description" content={description} />}
<meta name="twitter:title" content={title} />
{description && <meta name="twitter:description" content={description} />}
{hero && <meta name="twitter:image" content={hero} />}
<meta property="og:title" content={TITLE} key="title" />
{description && <meta property="og:description" content={description} />}
{hero && <meta property="og:image" content={hero} />}
</Head>
)
}

View file

@ -0,0 +1,135 @@
import { SearchResult } from '@/types/search-types'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useCallback, useEffect, useRef, useState } from 'react'
import { Icon } from './Icon'
export function Search({ activeId }: { activeId: string | null }) {
const [query, setQuery] = useState('')
const [results, setResults] = useState<SearchResult[]>([])
const rResultsList = useRef<HTMLOListElement>(null)
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setQuery(e.target.value)
}, [])
// eslint-disable-next-line react-hooks/exhaustive-deps
const sendQuery = useCallback(
throttle(async (query: string) => {
const res = await fetch(`/api/search?q=${query}&s=${activeId}`)
const json = await res.json()
setResults(json.results)
}, 150),
[activeId]
)
useEffect(() => {
const query = rInput.current!.value
if (query.length > 2) {
sendQuery(query)
} else {
setResults([])
}
}, [sendQuery])
const hasQuery = query.length > 0
const hasResults = query.length > 0
useEffect(() => {
function handleKeyUp(e: KeyboardEvent) {
if (e.key === 'Escape' && hasResults) {
setResults([])
}
}
function handleMouseUp(e: MouseEvent) {
if (rResultsList.current && !rResultsList.current.contains(e.target as Node)) {
setResults([])
}
}
document.body.addEventListener('mouseup', handleMouseUp)
document.body.addEventListener('keyup', handleKeyUp)
return () => {
document.body.removeEventListener('mouseup', handleMouseUp)
document.body.removeEventListener('keyup', handleKeyUp)
}
}, [hasResults])
const rInput = useRef<HTMLInputElement>(null)
const router = useRouter()
useEffect(() => {
setQuery('')
setResults([])
}, [router.asPath])
const handleFocus = useCallback(() => {
if (hasQuery && !hasResults) {
sendQuery(rInput.current!.value)
}
}, [sendQuery, hasQuery, hasResults])
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
router.push(`/search-results?q=${rInput.current!.value}`)
}
},
[router]
)
return (
<div className="search__wrapper">
<div className="search">
<Icon className="search__icon" icon="search" />
<input
ref={rInput}
type="text"
className="search__input"
placeholder="Search..."
value={query}
onChange={handleChange}
onFocus={handleFocus}
onKeyDown={handleKeyDown}
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
/>
</div>
{results.length > 0 && (
<div className="search__results__wrapper">
<div className="search__results">
<ol ref={rResultsList} className="search__results__list">
{results.map((result) => (
<Link key={result.id} href={result.url}>
<li className="sidebar__article search__results__article">
<h4>{result.subtitle}</h4>
<h3>{result.title}</h3>
</li>
</Link>
))}
</ol>
</div>
</div>
)}
</div>
)
}
function throttle<T extends (...args: any) => any>(
func: T,
limit: number
): (...args: Parameters<T>) => ReturnType<T> {
let inThrottle: boolean
let lastResult: ReturnType<T>
return function (this: any, ...args: any[]): ReturnType<T> {
if (!inThrottle) {
inThrottle = true
setTimeout(() => (inThrottle = false), limit)
lastResult = func(...args)
}
return lastResult
}
}

View file

@ -0,0 +1,151 @@
import {
SidebarContentArticleLink,
SidebarContentCategoryLink,
SidebarContentLink,
SidebarContentList,
SidebarContentSectionLink,
} from '@/types/content-types'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import { Icon } from './Icon'
import { Search } from './Search'
import { ThemeSwitcher } from './ThemeSwitcher'
type SidebarProps = SidebarContentList
export function Sidebar({ links, sectionId, categoryId, articleId }: SidebarProps) {
const [menuOpen, setMenuOpen] = useState(false)
const activeId = articleId ?? categoryId ?? sectionId
const router = useRouter()
useEffect(() => {
setMenuOpen(false)
}, [router.asPath])
return (
<>
<div className="sidebar" data-open={menuOpen}>
<div className="sidebar__buttons">
<ThemeSwitcher />
<div className="sidebar__buttons__socials">
<a
href="https://twitter.com/tldraw"
className="sidebar__button icon-button"
title="twitter"
>
<Icon icon="twitter" />
</a>
<a
href="https://github.com/tldraw/tldraw"
className="sidebar__button icon-button"
title="github"
>
<Icon icon="github" />
</a>
<a
href="https://discord.com/invite/SBBEVCA4PG"
className="sidebar__button icon-button"
title="discord"
>
<Icon icon="discord" />
</a>
</div>
</div>
<Search activeId={activeId} />
<nav className="sidebar__nav">
<ul className="sidebar__list sidebar__sections__list">
{links.map((link) => (
<SidebarLink key={link.url} {...link} />
))}
</ul>
</nav>
<div className="sidebar__footer">
<a href="https://www.tldraw.com">
<div
className="sidebar__lockup"
style={{
mask: `url(/lockup.svg) center 100% / 100% no-repeat`,
WebkitMask: `url(/lockup.svg) center 100% / 100% no-repeat`,
}}
/>
</a>
<div>tldraw © 2023</div>
</div>
<div className="sidebar__close">
<span onClick={() => setMenuOpen(false)}>Close</span>
<button className="icon-button" onClick={() => setMenuOpen(false)}>
<Icon icon="close" />
</button>
</div>
</div>
{/* Menu */}
<button className="menu__button icon-button" onClick={() => setMenuOpen(true)}>
<Icon icon="menu" />
</button>
</>
)
}
function SidebarLink(props: SidebarContentLink) {
switch (props.type) {
case 'section': {
return <SidebarSection {...props} />
}
case 'article': {
return <SidebarArticle {...props} />
}
case 'category': {
return <SidebarCategory {...props} />
}
}
}
function SidebarSection({ title, url, children }: SidebarContentSectionLink) {
return (
<li className="sidebar__section">
<Link href={url}>
<div className="sidebar__link sidebar__section__title" data-active={false}>
{title}
</div>
</Link>
<ul className="sidebar__list sidebar__section__list">
{children.map((link) => (
<SidebarLink key={link.url} {...link} />
))}
</ul>
</li>
)
}
function SidebarCategory({ title, url, children }: SidebarContentCategoryLink) {
return (
<li className="sidebar__category">
<Link href={url}>
<div className="sidebar__link sidebar__category__title" data-active={false}>
{title}
</div>
</Link>
<ul className="sidebar__list sidebar__category__list">
{children.map((link) => (
<SidebarLink key={link.url} {...link} />
))}
</ul>
<hr />
</li>
)
}
function SidebarArticle({ title, url }: SidebarContentArticleLink) {
return (
<li className="sidebar__article">
<Link href={url}>
<div className="sidebar__link sidebar__article__title" data-active={false}>
{title}
</div>
</Link>
</li>
)
}

View file

@ -0,0 +1,15 @@
import { useTheme } from 'next-themes'
import { Icon } from './Icon'
export function ThemeSwitcher() {
const { theme, setTheme } = useTheme()
return (
<button
className="sidebar__button icon-button"
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
>
<Icon icon="light" />
</button>
)
}

View file

@ -0,0 +1 @@
export {}

View file

@ -0,0 +1,27 @@
import { ReactNode } from 'react'
export function ParametersTable({ children }: { children: ReactNode }) {
return (
<table className="parametersTable">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>{children}</tbody>
</table>
)
}
export function ParametersTableRow({ children }: { children: ReactNode }) {
return <tr className="parametersTable-row">{children}</tr>
}
export function ParametersTableName({ children }: { children: ReactNode }) {
return <td className="parametersTable-name">{children}</td>
}
export function ParametersTableDescription({ children }: { children: ReactNode }) {
return <td className="parametersTable-description">{children}</td>
}

View file

@ -0,0 +1,124 @@
/* ---------------------- Lists --------------------- */
import { Icon } from '../Icon'
export const UnorderedList = (props: any) => {
return <ul {...props} />
}
export const OrderedList = (props: any) => {
return <ol {...props} />
}
export const ListItem = (props: any) => {
return <li {...props} />
}
/* ------------------- Typography ------------------- */
export const Heading1 = (props: any) => {
return <h1 {...props} />
}
export const Heading2 = (props: any) => {
return <h2 {...props} />
}
export const Heading3 = (props: any) => {
return (
<div className="article__title">
<h3 {...props} />
<button className="icon-button" onClick={() => window.scrollTo(0, 0)}>
<Icon icon="back-to-top" />
</button>
</div>
)
}
export const Heading4 = (props: any) => {
return <h4 {...props} />
}
export const Heading5 = (props: any) => {
return <h5 {...props} />
}
export const Heading6 = (props: any) => {
return <h6 {...props} />
}
export const Paragraph = (props: any) => {
return <p {...props} />
}
export const A = (props: any) => {
return <a {...props} />
}
export const Divider = (props: any) => {
return <hr {...props} />
}
export const Blockquote = (props: any) => {
return <blockquote {...props} />
}
export const Small = (props: any) => {
return (
<p className="article__small">
<small {...props} />
</p>
)
}
/* --------------------- Tables --------------------- */
export const Table = (props: any) => {
return <table {...props} />
}
export const THead = (props: any) => {
return <thead {...props} />
}
export const TR = (props: any) => {
return <tr {...props} />
}
export const TD = (props: any) => {
return <td {...props} />
}
/* --------------------- Media --------------------- */
export const Image = (props: any) => {
return (
<span className="artcle__image">
<img alt={props.title} {...props} />
{props.title && <span className="article__caption">{props.title}</span>}
</span>
)
}
export const Video = (props: any) => {
return (
<span className="artcle__video">
<video alt={props.title} {...props} />
{props.title && <span className="article__caption">{props.title}</span>}
</span>
)
}
/* ------------------- Code Blocks ------------------ */
export const Pre = (props: any) => {
return <pre {...props} />
}
export const Code = (props: any) => {
return <code {...props} />
}
export const Footnotes = (props: any) => {
return <div {...props} />
}

View file

@ -0,0 +1,51 @@
import * as customComponents from '../article-components'
import * as apiComponents from './api-docs'
import {
A,
Blockquote,
Divider,
Heading1,
Heading2,
Heading3,
Heading4,
Heading5,
Heading6,
Image,
ListItem,
OrderedList,
Paragraph,
Small,
Table,
TD,
THead,
TR,
UnorderedList,
Video,
} from './generic'
export const scope = {}
export const components = {
h1: Heading1,
h2: Heading2,
h3: Heading3,
h4: Heading4,
h5: Heading5,
h6: Heading6,
blockquote: Blockquote,
hr: Divider,
a: A,
p: Paragraph,
table: Table,
thead: THead,
tr: TR,
td: TD,
video: Video,
ol: OrderedList,
ul: UnorderedList,
li: ListItem,
img: Image,
Small: Small,
...customComponents,
...apiComponents,
}

4
apps/docs/content.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
declare module '*/content.json' {
const content: any
export default content
}

View file

View file

@ -0,0 +1,14 @@
{
"steveruizok": {
"name": "Steve Ruiz",
"email": "steve@tldraw.com",
"twitter": "steveruizok",
"image": "steve_ruiz.jpg"
},
"api": {
"name": "API",
"email": "hello@tldraw.com",
"twitter": "tldraw",
"image": "api.jpg"
}
}

View file

@ -0,0 +1,9 @@
---
title: Contributing
status: published
author: steveruizok
date: 3/22/2023
order: 0
---
Coming soon.

View file

@ -0,0 +1,9 @@
---
title: Embeds
status: published
author: steveruizok
date: 3/22/2023
order: 2
---
Coming soon.

View file

@ -0,0 +1,9 @@
---
title: Translations
status: published
author: steveruizok
date: 3/22/2023
order: 1
---
Coming soon.

View file

@ -0,0 +1,16 @@
---
title: App
status: published
author: steveruizok
date: 3/22/2023
order: 3
keywords:
- ui
- app
- control
- select
---
Coming soon.
See the [tldraw-examples repository](https://github.com/tldraw/tldraw-examples) for an example of how to use tldraw's App API to control the editor.

View file

@ -0,0 +1,9 @@
---
title: Collaboration
status: published
author: steveruizok
date: 3/22/2023
order: 7
---
Coming soon.

View file

@ -0,0 +1,18 @@
---
title: Installation
status: published
author: steveruizok
date: 3/22/2023
order: 1
---
First, install the `@tldraw/tldraw` package using `@alpha` for the **latest alpha release**. It also has peer dependencies on `signia` and `signia-react` which you will need to install at the same time.
```bash
yarn add @tldraw/tldraw@alpha signia signia-react
# or
npm install @tldraw/tldraw@alpha signia signia-react
```
Next, copy the following folders: `icons`, `embed-icons`, `fonts`, and `translations` from the [tldraw-examples](https://github.com/tldraw/tldraw-examples/tree/main/public) repository. Put them in your project's public path so that, e.g. `your-website.com/icons` points to the icons folder you copied. (Ability to customize the base asset URL is coming soon!)

View file

@ -0,0 +1,140 @@
---
title: Introduction
status: published
author: steveruizok
date: 3/22/2023
order: 0
---
Welcome to the tldraw developer docs.
tldraw is a React component that you can use to create infinite canvas experiences for the web. It is essentially the [tldraw.com](https://www.tldraw.com) application wrapped up and distributed as a React component, but with powerful APIs for creating custom shapes, tools, behaviors, and user interfaces elements.
![screenshot of tldraw](/images/screenshot.png)
These docs relate to tldraw's **alpha version**. This version is not yet open sourced, however it is available on npm and permissively licensed under Apache 2.0.
- Want to dive in? Visit the [examples CodeSandbox](https://codesandbox.io/p/github/tldraw/tldraw-examples/main?file=%2FREADME.md).
- Found a bug or integration problem? Please create a ticket [here](https://github.com/tldraw/tldraw-examples/issues).
- Questions or feedback? Let us know on the [Discord](https://discord.gg/JMbeb96jsh).
And if you are just looking for the regular app, try [tldraw.com](https://www.tldraw.com).
## Installation
First, install the `@tldraw/tldraw` package using `@alpha` for the **latest alpha release**. It also has peer dependencies on `signia` and `signia-react` which you will need to install at the same time.
```bash
yarn add @tldraw/tldraw@alpha signia signia-react
# or
npm install @tldraw/tldraw@alpha signia signia-react
```
Next, copy the following folders: `icons`, `embed-icons`, `fonts`, and `translations` from the [tldraw-examples](https://github.com/tldraw/tldraw-examples/tree/main/public) repository. Put them in your project's public path so that, e.g. `your-website.com/icons` points to the icons folder you copied. (Ability to customize the base asset URL is coming soon!)
## Usage
You should be able to use the `<Tldraw/>` component in any React app.
To use the `<Tldraw/>` component, create a file like this one:
```tsx
import { Tldraw } from '@tldraw/tldraw'
import '@tldraw/tldraw/editor.css'
import '@tldraw/tldraw/ui.css'
export default function () {
return (
<div
style={{
position: 'fixed',
inset: 0,
}}
>
<Tldraw />
</div>
)
}
```
In addition to the library, you will also need to:
- import the **CSS files** for the editor and UI
- have the library's **assets** available on the same host
- probably set a `viewport` meta tag in your **html**.
See below for more detail on these.
#### Next.js / SSR
The `<Tldraw/>` component cannot be server-rendered. If you're using the component in a Next.js app, you will need to import it dynamically. The code to do that will look something like this:
```tsx
import dynamic from "next/dynamic"
const Editor = dynamic(
async () => import('../components/Editor')),
{ ssr: false }
)
export default function MyPage() {
return <Editor/>
}
```
### `<Tldraw/>`
The `<Tldraw/>` component combines several other pieces:
- the tldraw editor (`@tldraw/editor`)
- the tldraw UI (`@tldraw/ui`)
- an engine (`@tldraw/sync-client`) for persistence and cross-tab synchronization
> **Note:** In the future, this library will also include an engine for using our collaboration services.
If you wanted to have more granular control, you could also use those subcomponents directly. See the ["exploded" example](https://github.com/tldraw/tldraw-examples/tree/main/src/examples/5-exploded) for what that would look like.
### Assets
In order to use the `<Tldraw/>` component, the app must be able to find certain assets on the host. These are contained in the `embed-icons`, `fonts`, `icons`, and `translations` folders. If you are using the `<Tldraw/>` component in your app, you must also copy these folders into your public path.
You can copy these files from the [tldraw-examples](https://github.com/tldraw/tldraw-examples) repository. Place the folders in your project's public path as shown in that repository.
> **Note:** This requirement is very likely to change in the near future.
While these files must be available, you can overwrite the individual files: for example, by placing different icons under the same name or modifying / adding translations.
### CSS
In order to use the `<Tldraw/>` component, you must also import a CSS file from the library `@tldraw/editor` library (automatically installed with `@tldraw/tldraw`):
```ts
import '@tldraw/tldraw/editor.css'
import '@tldraw/tldraw/ui.css'
```
You can overwrite these files with other CSS, customize the styles via package patches, or copy the contents into a different file and import that instead.
In addition to these stylesheets, the root project imports the `src/index.css` file, so its styles are used for every example. Be sure to take a look at this file: you may find some of these styles necessary in your own usage of the `<Tldraw/>` component.
### HTML
You probably also want to update your `index.html`'s meta viewport element as shown below.
```html
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
```
These may not be critical to `<Tldraw/>` performing correctly, however some features (such as safe area positioning) may not work correctly if these are not set.
## License
The tldraw libraries are licensed under Apache 2.0.
Our example repositories are licensed under MIT.
## Community
- Found a bug or integration problem? Please create a ticket [here](https://github.com/tldraw/tldraw-examples/issues).
- Questions or feedback? Let us know on the [Discord](https://discord.gg/JMbeb96jsh).
- Follow along on [Twitter](https://twitter.com/tldraw).

View file

@ -0,0 +1,18 @@
---
title: Persistence
status: published
author: steveruizok
date: 3/22/2023
order: 6
keywords:
- data
- sync
- persistence
- database
- indexeddb
- localstorage
---
Coming soon.
See the [tldraw-examples repository](https://github.com/tldraw/tldraw-examples) for an example of how to use the `@tldraw/tlsync-client` library to persist and sync between tabs.

View file

@ -0,0 +1,16 @@
---
title: Shapes
status: published
author: steveruizok
date: 3/22/2023
order: 4
keywords:
- custom
- shapes
- shapeutils
- utils
---
Coming soon.
See the [tldraw-examples repository](https://github.com/tldraw/tldraw-examples) for an example of how to create custom shapes in tldraw.

View file

@ -0,0 +1,15 @@
---
title: Tools
status: published
author: steveruizok
date: 3/22/2023
order: 5
keywords:
- custom
- tools
- state
---
Coming soon.
See the [tldraw-examples repository](https://github.com/tldraw/tldraw-examples) for an example of how to create custom tools in tldraw.

View file

@ -0,0 +1,100 @@
---
title: Usage
status: published
author: steveruizok
date: 3/22/2023
order: 2
---
You should be able to use the `<Tldraw/>` component in any React app.
To use the `<Tldraw/>` component, create a file like this one:
```tsx
import { Tldraw } from '@tldraw/tldraw'
import '@tldraw/tldraw/editor.css'
import '@tldraw/tldraw/ui.css'
export default function () {
return (
<div
style={{
position: 'fixed',
inset: 0,
}}
>
<Tldraw />
</div>
)
}
```
In addition to the library, you will also need to:
- import the **CSS files** for the editor and UI
- have the library's **assets** available on the same host
- probably set a `viewport` meta tag in your **html**.
See below for more detail on these.
#### A note on next.js / server-rendered react
The `<Tldraw/>` component cannot be server-rendered. If you're using the component in a Next.js app, you will need to import it dynamically. The code to do that will look something like this:
```tsx
import dynamic from "next/dynamic"
const Editor = dynamic(
async () => import('../components/Editor')),
{ ssr: false }
)
export default function MyPage() {
return <Editor/>
}
```
### `<Tldraw/>`
The `<Tldraw/>` component combines several other pieces:
- the tldraw editor (`@tldraw/editor`)
- the tldraw UI (`@tldraw/ui`)
- an engine (`@tldraw/sync-client`) for persistence and cross-tab syncronization
> **Note:** In the future, this library will also include an engine for using our collaboration services.
If you wanted to have more granular control, you could also use those subcomponents directly. See the ["exploded" example](https://github.com/tldraw/tldraw-examples) for what that would look like.
### Assets
In order to use the `<Tldraw/>` component, the app must be able to find certain assets on the host. These are contained in the `embed-icons`, `fonts`, `icons`, and `translations` folders. If you are using the `<Tldraw/>` component in your app, you must also copy these folders into your public path.
You can copy these files from the [tldraw-examples](https://github.com/tldraw/tldraw-examples) repository. Place the folders in your project's public path as shown in that repository.
> **Note:** This requirement is very likely to change in the near future.
While these files must be available, you can overwrite the individual files: for example, by placing different icons under the same name or modifying / adding translations.
### CSS
In order to use the `<Tldraw/>` component, you must also import a CSS file from the library `@tldraw/editor` library (automatically installed with `@tldraw/tldraw`):
```ts
import '@tldraw/tldraw/editor.css'
import '@tldraw/tldraw/ui.css'
```
You can overwrite these files with other CSS, customize the styles via package patches, or copy the contents into a different file and import that instead.
In addition to these stylesheets, the root project imports the `src/index.css` file, so its styles are used for every example. Be sure to take a look at this file: you may find some of these styles necessary in your own usage of the `<Tldraw/>` component.
### HTML
You probably also want to update your `index.html`'s meta viewport element as shown below.
```html
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
```
These may not be critical to `<Tldraw/>` performing correctly, however some features (such as safe area positioning) may not work correctly if these are not set.

View file

@ -0,0 +1,20 @@
---
title: User Interface
status: published
author: steveruizok
date: 3/22/2023
order: 8
keywords:
- ui
- interface
- tools
- shapes
- custom
- button
- toolbar
- styles
---
Coming soon.
See the [tldraw-examples repository](https://github.com/tldraw/tldraw-examples) for an example of how to customize tldraw's user interface.

View file

@ -0,0 +1,14 @@
[
{
"id": "docs",
"title": "Docs",
"description": "Developer documentation for tldraw.",
"categories": []
},
{
"id": "community",
"title": "Community",
"description": "Guides for contributing to tldraw's open source project.",
"categories": []
}
]

10
apps/docs/next.config.js Normal file
View file

@ -0,0 +1,10 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
experimental: {
scrollRestoration: true,
},
transpilePackages: ['@tldraw/utils'],
}
module.exports = nextConfig

76
apps/docs/package.json Normal file
View file

@ -0,0 +1,76 @@
{
"name": "@tldraw/docs",
"description": "A docs site for tldraw.",
"version": "0.1.1-alpha.0",
"private": true,
"packageManager": "yarn@3.5.0",
"author": {
"name": "tldraw GB Ltd.",
"email": "hello@tldraw.com"
},
"homepage": "https://tldraw.dev",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/tldraw/tldraw"
},
"bugs": {
"url": "https://github.com/tldraw/tldraw/issues"
},
"keywords": [
"tldraw",
"drawing",
"app",
"development",
"whiteboard",
"canvas",
"infinite"
],
"scripts": {
"dev": "lazy dev:docs",
"dev:docs": "next dev",
"build": "lazy build:docs",
"build:docs": "next build",
"start": "next start",
"lint": "yarn run -T tsx ../../scripts/lint.ts",
"docs:content": "ts-node --project tsconfig.content.json ./scripts/index.ts",
"content": "lazy docs:content"
},
"dependencies": {
"@microsoft/tsdoc": "^0.14.2",
"@tldraw/utils": "workspace:*",
"@types/node": "18.15.0",
"@types/react": "^18.0.24",
"@types/react-dom": "^18.0.6",
"eslint": "8.36.0",
"eslint-config-next": "13.2.4",
"gray-matter": "^4.0.3",
"next": "13.2.4",
"next-mdx-remote": "^4.4.1",
"next-remote-watch": "^2.0.0",
"next-themes": "^0.2.1",
"prettier": "^2.8.7",
"react": "18.2.0",
"react-dom": "18.2.0",
"rehype-autolink-headings": "^6.1.1",
"rehype-highlight": "^6.0.0",
"rehype-slug": "^5.1.0",
"rehype-slug-custom-id": "^1.1.0",
"remark-gfm": "^3.0.1"
},
"devDependencies": {
"@microsoft/api-extractor-model": "^7.26.4",
"@tldraw/editor": "workspace:*",
"@tldraw/file-format": "workspace:*",
"@tldraw/primitives": "workspace:*",
"@tldraw/tldraw": "workspace:*",
"@tldraw/tlschema": "workspace:*",
"@tldraw/tlstore": "workspace:*",
"@tldraw/tlsync-client": "workspace:*",
"@tldraw/tlvalidate": "workspace:*",
"@tldraw/ui": "workspace:*",
"lazyrepo": "0.0.0-alpha.20",
"rimraf": "^4.4.0",
"ts-node": "^10.9.1"
}
}

39
apps/docs/pages/404.tsx Normal file
View file

@ -0,0 +1,39 @@
import { Sidebar } from '@/components/Sidebar'
import { SidebarContentList } from '@/types/content-types'
import { getSidebarContentList } from '@/utils/getSidebarContentList'
import { GetStaticProps } from 'next'
import { useTheme } from 'next-themes'
import Link from 'next/link'
interface Props {
sidebar: SidebarContentList
}
export default function NotFoundpage({ sidebar }: Props) {
const theme = useTheme()
return (
<div className="layout">
<Sidebar {...sidebar} />
<main className={`article ${theme.theme ?? 'light'}`}>
<div
className="lockup"
style={{
mask: `url(/lockup.svg) center 100% / 100% no-repeat`,
WebkitMask: `url(/lockup.svg) center 100% / 100% no-repeat`,
}}
/>
<p>{`Sorry, we couldn't find the page you were looking for.`}</p>
<p>
<Link href="/">Back to the start.</Link>
</p>
</main>
</div>
)
}
export const getStaticProps: GetStaticProps<Props> = async () => {
const sidebar = await getSidebarContentList({})
return { props: { sidebar } }
}

View file

@ -0,0 +1,85 @@
import { ArticleDetails } from '@/components/ArticleDetails'
import { ArticleNavLinks } from '@/components/ArticleNavLinks'
import { Breadcrumb } from '@/components/Breadcrumb'
import { Mdx } from '@/components/Mdx'
import { MetaHead } from '@/components/MetaHead'
import { Sidebar } from '@/components/Sidebar'
import { Article, ArticleLinks, Category, Section, SidebarContentList } from '@/types/content-types'
import {
getArticle,
getArticleSource,
getCategory,
getLinks,
getSection,
getSections,
} from '@/utils/content'
import { getSidebarContentList } from '@/utils/getSidebarContentList'
import { GetStaticPaths, GetStaticProps } from 'next'
import { MDXRemoteSerializeResult } from 'next-mdx-remote'
import { useTheme } from 'next-themes'
interface Props {
sidebar: SidebarContentList
section: Section
category: Category
article: Article
links: ArticleLinks
mdxSource: MDXRemoteSerializeResult
}
export default function ArticlePage({
mdxSource,
section,
category,
article,
links,
sidebar,
}: Props) {
const theme = useTheme()
return (
<>
<MetaHead title={article.title} description={article.description} hero={article.hero} />
<div className="layout">
<Sidebar {...sidebar} />
<main className={`article ${theme.theme ?? 'light'}`}>
<Breadcrumb section={section} category={category} />
<h1>{article.title}</h1>
<Mdx mdxSource={mdxSource} />
<ArticleDetails article={article} />
<ArticleNavLinks links={links} />
</main>
</div>
</>
)
}
export const getStaticPaths: GetStaticPaths = async () => {
const sections = await getSections()
const paths: { params: { sectionId: string; categoryId: string; articleId: string } }[] = []
for (const section of sections) {
for (const category of section.categories) {
for (const articleId of category.articleIds) {
paths.push({ params: { sectionId: section.id, categoryId: category.id, articleId } })
}
}
}
return { paths, fallback: false }
}
export const getStaticProps: GetStaticProps<Props> = async (ctx) => {
const sectionId = ctx.params?.sectionId?.toString() as string
const categoryId = ctx.params?.categoryId?.toString() as string
const articleId = ctx.params?.articleId?.toString()
if (!articleId) throw Error()
const sidebar = await getSidebarContentList({ sectionId, categoryId, articleId })
const section = await getSection(sectionId)
const category = await getCategory(sectionId, categoryId)
const article = await getArticle(articleId)
const links = await getLinks(articleId)
const mdxSource = await getArticleSource(articleId)
return { props: { article, section, category, sidebar, links, mdxSource } }
}

View file

@ -0,0 +1,127 @@
import { Breadcrumb } from '@/components/Breadcrumb'
import { Mdx } from '@/components/Mdx'
import { MetaHead } from '@/components/MetaHead'
import { Sidebar } from '@/components/Sidebar'
import { Article, Category, Section, SidebarContentList } from '@/types/content-types'
import {
getArticleSource,
getArticles,
getCategory,
getSection,
getSections,
} from '@/utils/content'
import { getSidebarContentList } from '@/utils/getSidebarContentList'
import { GetStaticPaths, GetStaticProps } from 'next'
import { MDXRemoteSerializeResult } from 'next-mdx-remote'
import { useTheme } from 'next-themes'
import Link from 'next/link'
type Props = {
sidebar: SidebarContentList
section: Section
category: Category
articles: Article[]
mdxSource: MDXRemoteSerializeResult | null
}
export default function CategoryListPage({
sidebar,
mdxSource,
articles,
section,
category,
}: Props) {
const theme = useTheme()
const ungrouped: Article[] = []
const groupedArticles = Object.fromEntries(
category.groups.map((group) => [group.id, { group, articles: [] as Article[] }])
)
for (const article of articles) {
if (article.groupId) {
if (groupedArticles[article.groupId]) {
groupedArticles[article.groupId].articles.push(article)
} else {
throw Error(
`Article ${article.id} has groupId ${article.groupId} but no such group exists.`
)
}
} else {
ungrouped.push(article)
}
}
return (
<>
<MetaHead title={category.title} description={category.description} />
<div className="layout">
<Sidebar {...sidebar} />
<main className={`article list ${theme.theme ?? 'light'}`}>
<Breadcrumb section={section} />
<h1>{category.title}</h1>
{mdxSource && <Mdx mdxSource={mdxSource} />}
{Object.values(groupedArticles)
.filter((g) => g.articles.length > 0)
.map(({ group, articles }) => (
<>
<h2>{group.title}</h2>
<ul>
{articles.map((article) => (
<Link key={article.id} href={`/${section.id}/${category.id}/${article.id}`}>
<li>{article.title}</li>
</Link>
))}
</ul>
</>
))}
{ungrouped.length > 0 ? (
<ul>
{ungrouped.map((article) => (
<Link key={article.id} href={`/${section.id}/${category.id}/${article.id}`}>
<li>{article.title}</li>
</Link>
))}
</ul>
) : null}
</main>
</div>
</>
)
}
export const getStaticPaths: GetStaticPaths = async () => {
const sections = await getSections()
const paths: { params: { sectionId: string; categoryId: string } }[] = []
for (const section of sections) {
if (section.categories) {
for (const category of section.categories) {
paths.push({ params: { sectionId: section.id, categoryId: category.id } })
}
}
}
return { paths, fallback: false }
}
export const getStaticProps: GetStaticProps<Props> = async (ctx) => {
const sectionId = ctx.params?.sectionId?.toString() as string
const categoryId = ctx.params?.categoryId?.toString()
if (!categoryId || !sectionId) throw Error()
const sidebar = await getSidebarContentList({
sectionId,
categoryId,
})
const articles = await getArticles()
const section = await getSection(sectionId)
const category = await getCategory(sectionId, categoryId)
const categoryArticles = category.articleIds.map((id) => articles[id])
const article = articles[categoryId + '_index'] ?? null
const mdxSource = article ? await getArticleSource(categoryId + '_index') : null
return { props: { sidebar, section, category, articles: categoryArticles, mdxSource } }
}

View file

@ -0,0 +1,91 @@
import { Breadcrumb } from '@/components/Breadcrumb'
import { Mdx } from '@/components/Mdx'
import { MetaHead } from '@/components/MetaHead'
import { Sidebar } from '@/components/Sidebar'
import { Article, Category, Section, SidebarContentList } from '@/types/content-types'
import { getArticleSource, getArticles, getSection, getSections } from '@/utils/content'
import { getSidebarContentList } from '@/utils/getSidebarContentList'
import { GetStaticPaths, GetStaticProps } from 'next'
import { MDXRemoteSerializeResult } from 'next-mdx-remote'
import { useTheme } from 'next-themes'
import Link from 'next/link'
type Props = {
sidebar: SidebarContentList
section: Section
categories: { category: Category; articles: Article[] }[]
mdxSource: MDXRemoteSerializeResult | null
}
export default function SectionListPage({ sidebar, section, categories, mdxSource }: Props) {
const theme = useTheme()
const ucg = categories.find((category) => category.category.id === 'ucg')!
return (
<>
<MetaHead title={section.title} description={section.description} />
<div className="layout">
<Sidebar {...sidebar} />
<main className={`article list ${theme.theme ?? 'light'}`}>
<Breadcrumb />
<h1>{section.title}</h1>
{mdxSource && <Mdx mdxSource={mdxSource} />}
{ucg.articles.length > 0 ? (
<>
<ul>
{ucg.articles.map((article) => {
return (
<Link key={article.id} href={`/${section.id}/${ucg.category.id}/${article.id}`}>
<li>{article.title}</li>
</Link>
)
})}
</ul>
</>
) : null}
<ul>
{categories.map((category) =>
category.category.id === 'ucg' ? null : (
<Link key={category.category.id} href={`/${section.id}/${category.category.id}`}>
{category.category.id === 'ucg' ? null : <li>{category.category.title}</li>}
</Link>
)
)}
</ul>
</main>
</div>
</>
)
}
export const getStaticPaths: GetStaticPaths = async () => {
const sections = await getSections()
const paths: { params: { sectionId: string } }[] = []
for (const section of sections) {
paths.push({ params: { sectionId: section.id } })
}
return { paths, fallback: false }
}
export const getStaticProps: GetStaticProps<Props> = async (ctx) => {
const sectionId = ctx.params?.sectionId?.toString()
if (!sectionId) throw Error()
const sidebar = await getSidebarContentList({
sectionId,
})
const articles = await getArticles()
const section = await getSection(sectionId)
const categories = [] as { category: Category; articles: Article[] }[]
for (const category of section.categories) {
categories.push({ category, articles: category.articleIds.map((id) => articles[id]) })
}
const article = articles[sectionId + '_index'] ?? null
const mdxSource = article ? await getArticleSource(sectionId + '_index') : null
return { props: { sidebar, section, categories, mdxSource } }
}

17
apps/docs/pages/_app.tsx Normal file
View file

@ -0,0 +1,17 @@
import '@/styles/globals.css'
import { ThemeProvider } from 'next-themes'
import type { AppProps } from 'next/app'
import Head from 'next/head'
export default function App({ Component, pageProps }: AppProps) {
return (
<ThemeProvider>
<Head>
<title>tldraw docs</title>
<meta charSet="utf-8" />
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<Component {...pageProps} />
</ThemeProvider>
)
}

View file

@ -0,0 +1,63 @@
import { Head, Html, Main, NextScript } from 'next/document'
const TITLE = 'tldraw'
const DESCRIPTION =
'Developer documentation for tldraw. Build infinite canvas experiences for the web.'
const URL = 'https://docs.tldraw.dev'
const TWITTER_HANDLE = '@tldraw'
const TWITTER_CARD = 'social-twitter.png'
const FACEBOOK_CARD = 'social-og.png'
const THEME_COLOR = '#FFFFFF'
const THEME_ACCENT = '#FFFFFF'
export default function Document() {
return (
<Html lang="en">
<Head>
<meta name="application-name" content={TITLE} />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content={TITLE} />
<meta name="description" content={DESCRIPTION} />
<meta name="format-detection" content="telephone=no" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="msapplication-config" content="browserconfig.xml" />
<meta name="msapplication-TileColor" content={THEME_ACCENT} />
<meta name="msapplication-tap-highlight" content="no" />
<meta name="theme-color" content={THEME_COLOR} />
<link rel="apple-touch-icon" href="touch-icon-iphone.png" />
<link rel="apple-touch-icon" sizes="152x152" href="apple-touch-icon-152x152.png" />
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon-180x180.png" />
<link rel="apple-touch-icon" sizes="167x167" href="apple-touch-icon-167x167.png" />
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png" />
<link rel="mask-icon" href="safari-pinned-tab.svg" color={THEME_COLOR} />
<link rel="shortcut icon" href="/favicon.svg" />
<link rel="manifest" href="/manifest.json" />
{/* Twitter */}
<meta name="twitter:card" content="summary" />
<meta name="twitter:url" content={URL} />
<meta name="twitter:title" content={TITLE} />
<meta name="twitter:description" content={DESCRIPTION} />
<meta name="twitter:image" content={`${URL}/${TWITTER_CARD}`} />
<meta name="twitter:creator" content={TWITTER_HANDLE} />
{/* Facebook */}
<meta property="og:type" content="website" />
<meta property="og:title" content={TITLE} />
<meta property="og:description" content={DESCRIPTION} />
<meta property="og:site_name" content={TITLE} />
<meta property="og:url" content={URL} />
<meta property="og:image" content={`${URL}/${FACEBOOK_CARD}`} />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}

View file

@ -0,0 +1,45 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import { SearchResult } from '@/types/search-types'
import { getArticles, getSections } from '@/utils/content'
import type { NextApiRequest, NextApiResponse } from 'next'
type Data = {
results: SearchResult[]
}
export default async function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
const { q, s } = req.query
const query = q?.toString().toLowerCase()
const activeId = s?.toString()
if (!query) return res.status(400).json({ results: [] })
const results: Data['results'] = []
const articles = await getArticles()
for (const section of await getSections()) {
for (const category of section.categories) {
for (const articleId of category.articleIds) {
if (activeId === articleId) continue
const article = articles[articleId]
if (
article.title.toLowerCase().includes(query) ||
(article.description && article.description.toLowerCase().includes(query)) ||
article.keywords.includes(query)
) {
results.push({
id: article.id,
type: 'article',
subtitle:
category.id === 'ucg' ? `${section.title}` : `${section.title} / ${category.title}`,
title: article.title,
url: `${section.id}/${category.id}/${article.id}`,
})
}
}
}
}
res.status(200).json({ results })
}

52
apps/docs/pages/index.tsx Normal file
View file

@ -0,0 +1,52 @@
import { ArticleDetails } from '@/components/ArticleDetails'
import { ArticleNavLinks } from '@/components/ArticleNavLinks'
import { Mdx } from '@/components/Mdx'
import { Sidebar } from '@/components/Sidebar'
import { Article, ArticleLinks, SidebarContentList } from '@/types/content-types'
import { getArticle, getArticleSource, getLinks } from '@/utils/content'
import { getSidebarContentList } from '@/utils/getSidebarContentList'
import { GetStaticProps } from 'next'
import { MDXRemoteSerializeResult } from 'next-mdx-remote'
import { useTheme } from 'next-themes'
interface Props {
article: Article
links: ArticleLinks
sidebar: SidebarContentList
mdxSource: MDXRemoteSerializeResult
}
export default function ArticlePage({ mdxSource, links, sidebar, article }: Props) {
const theme = useTheme()
return (
<div className="layout">
<Sidebar {...sidebar} />
<main className={`article ${theme.theme ?? 'light'}`}>
<div
className="lockup"
style={{
mask: `url(/lockup.svg) center 100% / 100% no-repeat`,
WebkitMask: `url(/lockup.svg) center 100% / 100% no-repeat`,
}}
/>
<Mdx mdxSource={mdxSource} />
<ArticleDetails article={article} />
<ArticleNavLinks links={links} />
</main>
</div>
)
}
const sectionId = 'docs'
const categoryId = 'ucg'
const articleId = 'introduction'
export const getStaticProps: GetStaticProps<Props> = async () => {
const sidebar = await getSidebarContentList({ sectionId, categoryId, articleId })
const article = await getArticle(articleId)
const links = await getLinks(articleId)
const mdxSource = await getArticleSource(articleId)
return { props: { article, sidebar, links, mdxSource } }
}

View file

@ -0,0 +1,58 @@
import { Breadcrumb } from '@/components/Breadcrumb'
import { MetaHead } from '@/components/MetaHead'
import { Sidebar } from '@/components/Sidebar'
import { SidebarContentList } from '@/types/content-types'
import { SearchResult } from '@/types/search-types'
import { getSidebarContentList } from '@/utils/getSidebarContentList'
import { GetServerSideProps } from 'next'
import { useTheme } from 'next-themes'
import Link from 'next/link'
type Props = {
sidebar: SidebarContentList
results: SearchResult[]
query: string
}
export default function SectionListPage({ sidebar, query, results }: Props) {
const theme = useTheme()
return (
<>
<MetaHead title={`Search Results: ${query} (${results.length})`} />
<div className="layout">
<Sidebar {...sidebar} />
<main className={`article list ${theme.theme ?? 'light'}`}>
<Breadcrumb />
<h1>{`Found ${results.length} Results for "${query}"`}</h1>
<ul>
{results.map((result) => (
<Link key={result.id} href={`${result.url}`}>
<li>
<h4>{result.subtitle}</h4>
<h3>{result.title}</h3>
</li>
</Link>
))}
</ul>
</main>
</div>
</>
)
}
export const getServerSideProps: GetServerSideProps<Props> = async (ctx) => {
// get the q out of search-results?q=foo
const query = ctx.query?.q?.toString() as string
if (!query) throw Error()
const sidebar = await getSidebarContentList({})
// fetch from our current server
const res = await fetch(`http://${ctx.req.headers.host}/api/search?q=${query}`)
const json = await res.json()
const results = json.results
return { props: { sidebar, query, results } }
}

View file

@ -0,0 +1,11 @@
<svg width="152" height="152" viewBox="0 0 152 152" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1479_150769)">
<path d="M0 16.7033C0 7.47832 7.16344 0 16 0H136C144.837 0 152 7.47832 152 16.7033V135.297C152 144.522 144.837 152 136 152H16C7.16345 152 0 144.522 0 135.297V16.7033Z" fill="black"/>
<path d="M88.5463 43.4721C88.5463 47.1455 87.2907 50.2624 84.7797 52.8226C82.2686 55.3829 79.2116 56.663 75.6088 56.663C71.8968 56.663 68.7852 55.3829 66.2741 52.8226C63.7631 50.2624 62.5075 47.1455 62.5075 43.4721C62.5075 39.7987 63.7631 36.6819 66.2741 34.1216C68.7852 31.5614 71.8968 30.2812 75.6088 30.2812C79.2116 30.2812 82.2686 31.5614 84.7797 34.1216C87.2907 36.6819 88.5463 39.7987 88.5463 43.4721ZM62.3438 89.7004C62.3438 86.027 63.5993 82.9102 66.1104 80.3499C68.7306 77.6783 71.8968 76.3425 75.6088 76.3425C79.1025 76.3425 82.1594 77.6783 84.7797 80.3499C87.3999 82.9102 88.9284 85.8043 89.3651 89.0325C90.2385 95.0435 89.1468 100.999 86.0898 106.899C83.142 112.798 78.8841 117.307 73.3161 120.423C70.2591 122.204 67.748 122.149 65.7828 120.256C63.9268 118.475 64.4727 116.36 67.4205 113.911C69.0581 112.687 70.4229 111.129 71.5146 109.236C72.6064 107.344 73.3161 105.396 73.6436 103.392C73.7528 102.502 73.3706 102.056 72.4972 102.056C70.3137 101.945 68.0756 100.721 65.7828 98.383C63.4901 96.0454 62.3438 93.1512 62.3438 89.7004Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_1479_150769">
<rect width="152" height="152" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,11 @@
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1479_150767)">
<path d="M0 19.7802C0 8.85591 8.48303 0 18.9474 0H161.053C171.517 0 180 8.85591 180 19.7802V160.22C180 171.144 171.517 180 161.053 180H18.9474C8.48303 180 0 171.144 0 160.22V19.7802Z" fill="black"/>
<path d="M104.857 51.4801C104.857 55.8302 103.371 59.5212 100.397 62.5531C97.4233 65.585 93.8032 67.1009 89.5367 67.1009C85.1409 67.1009 81.4562 65.585 78.4825 62.5531C75.5089 59.5212 74.0221 55.8302 74.0221 51.4801C74.0221 47.1301 75.5089 43.4391 78.4825 40.4072C81.4562 37.3753 85.1409 35.8594 89.5367 35.8594C93.8032 35.8594 97.4233 37.3753 100.397 40.4072C103.371 43.4391 104.857 47.1301 104.857 51.4801ZM73.8281 106.224C73.8281 101.874 75.3149 98.1831 78.2886 95.1512C81.3915 91.9875 85.1409 90.4056 89.5367 90.4056C93.674 90.4056 97.294 91.9875 100.397 95.1512C103.5 98.1831 105.31 101.61 105.827 105.433C106.861 112.552 105.569 119.604 101.948 126.59C98.4576 133.577 93.4154 138.916 86.8216 142.607C83.2016 144.716 80.2279 144.65 77.9007 142.409C75.7028 140.3 76.3493 137.795 79.8401 134.895C81.7794 133.445 83.3955 131.6 84.6884 129.359C85.9813 127.118 86.8217 124.811 87.2095 122.438C87.3388 121.384 86.8863 120.856 85.852 120.856C83.2662 120.724 80.6158 119.274 77.9007 116.506C75.1857 113.738 73.8281 110.311 73.8281 106.224Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_1479_150767">
<rect width="180" height="180" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,11 @@
<svg width="167" height="167" viewBox="0 0 167 167" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1479_150768)">
<path d="M0 18.3516C0 8.21631 7.87036 0 17.5789 0H149.421C159.13 0 167 8.21631 167 18.3516V148.648C167 158.784 159.13 167 149.421 167H17.579C7.87037 167 0 158.784 0 148.648V18.3516Z" fill="black"/>
<path d="M97.2844 47.7621C97.2844 51.7981 95.905 55.2225 93.1461 58.0354C90.3872 60.8483 87.0286 62.2547 83.0702 62.2547C78.9918 62.2547 75.5732 60.8483 72.8143 58.0354C70.0555 55.2225 68.676 51.7981 68.676 47.7621C68.676 43.7262 70.0555 40.3018 72.8143 37.4889C75.5732 34.676 78.9918 33.2695 83.0702 33.2695C87.0286 33.2695 90.3872 34.676 93.1461 37.4889C95.905 40.3018 97.2844 43.7262 97.2844 47.7621ZM68.4961 98.5524C68.4961 94.5165 69.8755 91.0921 72.6344 88.2792C75.5132 85.344 78.9918 83.8763 83.0702 83.8763C86.9086 83.8763 90.2673 85.344 93.1461 88.2792C96.0249 91.0921 97.7042 94.2719 98.184 97.8186C99.1436 104.423 97.9441 110.966 94.5855 117.448C91.3468 123.93 86.6687 128.883 80.5512 132.307C77.1926 134.264 74.4337 134.203 72.2746 132.124C70.2354 130.167 70.8351 127.843 74.0738 125.153C75.8731 123.807 77.3725 122.095 78.572 120.016C79.7715 117.937 80.5512 115.797 80.9111 113.595C81.031 112.617 80.6112 112.128 79.6516 112.128C77.2525 112.005 74.7935 110.66 72.2746 108.092C69.7556 105.524 68.4961 102.344 68.4961 98.5524Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_1479_150768">
<rect width="167" height="167" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

View file

@ -0,0 +1,11 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1479_150771)">
<path d="M0 1.75824C0 0.787192 0.754047 0 1.68421 0H14.3158C15.246 0 16 0.787192 16 1.75824V14.2418C16 15.2128 15.246 16 14.3158 16H1.68421C0.754047 16 0 15.2128 0 14.2418V1.75824Z" fill="black"/>
<path d="M9.32066 4.57601C9.32066 4.96269 9.1885 5.29078 8.92418 5.56028C8.65985 5.82978 8.33807 5.96453 7.95882 5.96453C7.56808 5.96453 7.24055 5.82978 6.97622 5.56028C6.7119 5.29078 6.57974 4.96269 6.57974 4.57601C6.57974 4.18934 6.7119 3.86125 6.97622 3.59175C7.24055 3.32225 7.56808 3.1875 7.95882 3.1875C8.33807 3.1875 8.65985 3.32225 8.92418 3.59175C9.1885 3.86125 9.32066 4.18934 9.32066 4.57601ZM6.5625 9.44215C6.5625 9.05547 6.69466 8.72738 6.95899 8.45788C7.2348 8.17667 7.56808 8.03606 7.95882 8.03606C8.32657 8.03606 8.64836 8.17667 8.92418 8.45788C9.19999 8.72738 9.36088 9.03204 9.40685 9.37184C9.49879 10.0046 9.38387 10.6315 9.06208 11.2525C8.75179 11.8735 8.30359 12.3481 7.71748 12.6762C7.39569 12.8636 7.13137 12.8578 6.92451 12.6586C6.72914 12.4711 6.7866 12.2485 7.09689 11.9907C7.26928 11.8618 7.41293 11.6977 7.52786 11.4986C7.64278 11.2994 7.71748 11.0943 7.75196 10.8834C7.76345 10.7896 7.72323 10.7428 7.63129 10.7428C7.40144 10.7311 7.16585 10.6022 6.92451 10.3561C6.68317 10.11 6.5625 9.80539 6.5625 9.44215Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_1479_150771">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,11 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1479_150770)">
<path d="M0 3.51648C0 1.57438 1.50809 0 3.36842 0H28.6316C30.4919 0 32 1.57438 32 3.51648V28.4835C32 30.4256 30.4919 32 28.6316 32H3.36842C1.50809 32 0 30.4256 0 28.4835V3.51648Z" fill="black"/>
<path d="M18.6413 9.15203C18.6413 9.92538 18.377 10.5816 17.8484 11.1206C17.3197 11.6596 16.6761 11.9291 15.9176 11.9291C15.1362 11.9291 14.4811 11.6596 13.9524 11.1206C13.4238 10.5816 13.1595 9.92538 13.1595 9.15203C13.1595 8.37868 13.4238 7.7225 13.9524 7.1835C14.4811 6.6445 15.1362 6.375 15.9176 6.375C16.6761 6.375 17.3197 6.6445 17.8484 7.1835C18.377 7.7225 18.6413 8.37868 18.6413 9.15203ZM13.125 18.8843C13.125 18.1109 13.3893 17.4548 13.918 16.9158C14.4696 16.3533 15.1362 16.0721 15.9176 16.0721C16.6531 16.0721 17.2967 16.3533 17.8484 16.9158C18.4 17.4548 18.7218 18.0641 18.8137 18.7437C18.9976 20.0092 18.7677 21.2629 18.1242 22.505C17.5036 23.747 16.6072 24.6961 15.435 25.3523C14.7914 25.7273 14.2627 25.7155 13.849 25.3172C13.4583 24.9422 13.5732 24.4969 14.1938 23.9814C14.5386 23.7236 14.8259 23.3955 15.0557 22.9971C15.2856 22.5987 15.435 22.1886 15.5039 21.7668C15.5269 21.5793 15.4465 21.4856 15.2626 21.4856C14.8029 21.4621 14.3317 21.2043 13.849 20.7122C13.3663 20.2201 13.125 19.6108 13.125 18.8843Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_1479_150770">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,4 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.36842 0.5H28.6316C30.1957 0.5 31.5 1.83004 31.5 3.51648V28.4835C31.5 30.17 30.1957 31.5 28.6316 31.5H3.36842C1.80429 31.5 0.5 30.17 0.5 28.4835V3.51648C0.5 1.83004 1.80429 0.5 3.36842 0.5Z" fill="black"/>
<path d="M18.6413 9.15203C18.6413 9.92538 18.377 10.5816 17.8484 11.1206C17.3197 11.6596 16.6761 11.9291 15.9176 11.9291C15.1362 11.9291 14.4811 11.6596 13.9524 11.1206C13.4238 10.5816 13.1595 9.92538 13.1595 9.15203C13.1595 8.37868 13.4238 7.7225 13.9524 7.1835C14.4811 6.6445 15.1362 6.375 15.9176 6.375C16.6761 6.375 17.3197 6.6445 17.8484 7.1835C18.377 7.7225 18.6413 8.37868 18.6413 9.15203ZM13.125 18.8843C13.125 18.1109 13.3893 17.4548 13.918 16.9158C14.4696 16.3533 15.1362 16.0721 15.9176 16.0721C16.6531 16.0721 17.2967 16.3533 17.8484 16.9158C18.4 17.4548 18.7218 18.0641 18.8137 18.7437C18.9976 20.0092 18.7677 21.2629 18.1242 22.505C17.5036 23.747 16.6072 24.6961 15.435 25.3523C14.7914 25.7273 14.2627 25.7155 13.849 25.3172C13.4583 24.9422 13.5732 24.4969 14.1938 23.9814C14.5386 23.7236 14.8259 23.3955 15.0557 22.9971C15.2856 22.5987 15.435 22.1886 15.5039 21.7668C15.5269 21.5793 15.4465 21.4856 15.2626 21.4856C14.8029 21.4621 14.3317 21.2043 13.849 20.7122C13.3663 20.2201 13.125 19.6108 13.125 18.8843Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.85355 3.85355C7.04882 3.65829 7.04882 3.34171 6.85355 3.14645C6.65829 2.95118 6.34171 2.95118 6.14645 3.14645L2.14645 7.14645C1.95118 7.34171 1.95118 7.65829 2.14645 7.85355L6.14645 11.8536C6.34171 12.0488 6.65829 12.0488 6.85355 11.8536C7.04882 11.6583 7.04882 11.3417 6.85355 11.1464L3.20711 7.5L6.85355 3.85355ZM12.8536 3.85355C13.0488 3.65829 13.0488 3.34171 12.8536 3.14645C12.6583 2.95118 12.3417 2.95118 12.1464 3.14645L8.14645 7.14645C7.95118 7.34171 7.95118 7.65829 8.14645 7.85355L12.1464 11.8536C12.3417 12.0488 12.6583 12.0488 12.8536 11.8536C13.0488 11.6583 13.0488 11.3417 12.8536 11.1464L9.20711 7.5L12.8536 3.85355Z" fill="#2D2D2D"/>
</svg>

After

Width:  |  Height:  |  Size: 805 B

View file

@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.14645 10.6464C1.95118 10.8417 1.95118 11.1583 2.14645 11.3536C2.34171 11.5488 2.65829 11.5488 2.85355 11.3536L6.85355 7.35355C7.04882 7.15829 7.04882 6.84171 6.85355 6.64645L2.85355 2.64645C2.65829 2.45118 2.34171 2.45118 2.14645 2.64645C1.95118 2.84171 1.95118 3.15829 2.14645 3.35355L5.79289 7L2.14645 10.6464ZM8.14645 10.6464C7.95118 10.8417 7.95118 11.1583 8.14645 11.3536C8.34171 11.5488 8.65829 11.5488 8.85355 11.3536L12.8536 7.35355C13.0488 7.15829 13.0488 6.84171 12.8536 6.64645L8.85355 2.64645C8.65829 2.45118 8.34171 2.45118 8.14645 2.64645C7.95118 2.84171 7.95118 3.15829 8.14645 3.35355L11.7929 7L8.14645 10.6464Z" fill="#2D2D2D"/>
</svg>

After

Width:  |  Height:  |  Size: 801 B

View file

@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.8536 9.85355C11.6583 10.0488 11.3417 10.0488 11.1464 9.85355L7.5 6.20711L3.85355 9.85355C3.65829 10.0488 3.34171 10.0488 3.14645 9.85355C2.95118 9.65829 2.95118 9.34171 3.14645 9.14645L7.14645 5.14645C7.34171 4.95118 7.65829 4.95118 7.85355 5.14645L11.8536 9.14645C12.0488 9.34171 12.0488 9.65829 11.8536 9.85355Z" fill="#2D2D2D"/>
</svg>

After

Width:  |  Height:  |  Size: 488 B

View file

@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.8536 2.85355C13.0488 2.65829 13.0488 2.34171 12.8536 2.14645C12.6583 1.95118 12.3417 1.95118 12.1464 2.14645L7.5 6.79289L2.85355 2.14645C2.65829 1.95118 2.34171 1.95118 2.14645 2.14645C1.95118 2.34171 1.95118 2.65829 2.14645 2.85355L6.79289 7.5L2.14645 12.1464C1.95118 12.3417 1.95118 12.6583 2.14645 12.8536C2.34171 13.0488 2.65829 13.0488 2.85355 12.8536L7.5 8.20711L12.1464 12.8536C12.3417 13.0488 12.6583 13.0488 12.8536 12.8536C13.0488 12.6583 13.0488 12.3417 12.8536 12.1464L8.20711 7.5L12.8536 2.85355Z" fill="#2D2D2D"/>
</svg>

After

Width:  |  Height:  |  Size: 684 B

View file

@ -0,0 +1,12 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1463_121087)">
<path d="M4.08049 7.01221C4.32412 6.74984 4.65476 6.60162 5.00007 6.59998C5.34538 6.60162 5.67603 6.74984 5.91966 7.01221C6.16329 7.27459 6.30007 7.62974 6.30007 7.99998C6.30007 8.37021 6.16329 8.72536 5.91966 8.98774C5.67603 9.25011 5.34538 9.39833 5.00007 9.39998C4.65476 9.39833 4.32412 9.25011 4.08049 8.98774C3.83685 8.72536 3.70007 8.37021 3.70007 7.99998C3.70007 7.62974 3.83685 7.27459 4.08049 7.01221Z" fill="#2D2D2D"/>
<path d="M9.07926 7.01221C9.3229 6.74984 9.65354 6.60162 9.99885 6.59998C10.3442 6.60162 10.6748 6.74984 10.9184 7.01221C11.1621 7.27459 11.2989 7.62974 11.2989 7.99998C11.2989 8.37021 11.1621 8.72536 10.9184 8.98774C10.6748 9.25011 10.3442 9.39833 9.99885 9.39998C9.65354 9.39833 9.3229 9.25011 9.07926 8.98774C8.83563 8.72536 8.69885 8.37021 8.69885 7.99998C8.69885 7.62974 8.83563 7.27459 9.07926 7.01221Z" fill="#2D2D2D"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.94586 1.80299C4.98973 1.79821 5.03403 1.79926 5.07761 1.80611C5.28859 1.83926 5.46936 1.94778 5.60153 2.04762C5.7413 2.15321 5.87082 2.28296 5.98238 2.41388C6.14069 2.59967 6.29436 2.8255 6.39564 3.04143C6.73494 3.01472 7.10264 3.00003 7.5 3.00003C7.89645 3.00003 8.26336 3.01465 8.602 3.04125C8.70328 2.82537 8.85692 2.59961 9.01518 2.41388C9.12674 2.28296 9.25626 2.15321 9.39603 2.04762C9.5282 1.94778 9.70897 1.83926 9.91994 1.80611C9.96353 1.79926 10.0078 1.79821 10.0517 1.80299C10.8961 1.89496 12.1787 2.34982 12.9359 2.76053C12.9881 2.78883 13.0348 2.8262 13.0739 2.87088C13.39 3.23226 13.6756 3.82072 13.899 4.37441C14.129 4.94414 14.3194 5.54458 14.428 5.97872C14.7812 7.39064 14.975 9.08579 15.0001 10.6923C15.0014 10.776 14.9816 10.8587 14.9427 10.9328C14.639 11.5102 13.9887 12.0436 13.3398 12.4324C12.6832 12.8258 11.9163 13.1378 11.2956 13.1977C11.1399 13.2128 10.9861 13.154 10.88 13.039C10.7243 12.8702 10.4611 12.478 10.2634 12.1753C10.1767 12.0426 10.0954 11.9158 10.0297 11.8126C9.3688 11.9317 8.54207 12.0002 7.50002 12.0002C6.45681 12.0002 5.62939 11.9315 4.96812 11.8122C4.90241 11.9155 4.82098 12.0425 4.73419 12.1753C4.53643 12.478 4.2733 12.8702 4.11752 13.039C4.01144 13.154 3.85765 13.2128 3.70193 13.1977C3.08127 13.1378 2.31435 12.8258 1.65781 12.4324C1.00888 12.0436 0.358561 11.5102 0.0548912 10.9328C0.0159279 10.8587 -0.00380681 10.776 -0.00250281 10.6923C0.0225233 9.08579 0.216388 7.39064 0.569535 5.97872C0.678122 5.54458 0.868566 4.94414 1.09852 4.37441C1.32199 3.82072 1.60757 3.23226 1.92364 2.87088C1.96272 2.8262 2.00944 2.78883 2.06162 2.76053C2.81887 2.34982 4.10147 1.89496 4.94586 1.80299ZM3.94943 11.5479C3.93225 11.5743 3.91476 11.6012 3.89705 11.6283C3.76696 11.8274 3.63866 12.0178 3.53827 12.1584C3.14641 12.0683 2.64965 11.8609 2.17176 11.5746C1.63205 11.2512 1.20654 10.881 0.99999 10.5682C1.03284 9.06493 1.21857 7.50509 1.53965 6.22136C1.63669 5.8334 1.81264 5.2769 2.02583 4.74869C2.22582 4.25319 2.43795 3.83541 2.62267 3.59495C3.26952 3.2605 4.28449 2.90752 4.94803 2.81071C4.96112 2.81866 4.97802 2.82988 4.99875 2.84554C5.06541 2.89589 5.14282 2.97044 5.22123 3.06246C5.25511 3.10222 5.28654 3.14206 5.31532 3.18102C5.02277 3.2357 4.76083 3.30049 4.52795 3.37214C3.84282 3.58295 3.34501 3.87336 3.1 4.20003C2.93431 4.42094 2.97909 4.73434 3.2 4.90003C3.42091 5.06572 3.73431 5.02094 3.9 4.80003C3.95499 4.7267 4.20717 4.51711 4.82204 4.32792C5.41424 4.1457 6.28795 4.00003 7.5 4.00003C8.71205 4.00003 9.58576 4.1457 10.178 4.32792C10.7928 4.51711 11.045 4.7267 11.1 4.80003C11.2657 5.02094 11.5791 5.06572 11.8 4.90003C12.0209 4.73434 12.0657 4.42094 11.9 4.20003C11.655 3.87336 11.1572 3.58295 10.472 3.37214C10.2386 3.30031 9.97595 3.23538 9.68254 3.18062C9.71124 3.14179 9.74257 3.10208 9.77633 3.06246C9.85474 2.97044 9.93215 2.89589 9.9988 2.84554C10.0195 2.82988 10.0364 2.81866 10.0495 2.81071C10.7131 2.90752 11.728 3.2605 12.3749 3.59495C12.5596 3.83541 12.7717 4.25319 12.9717 4.74869C13.1849 5.2769 13.3609 5.8334 13.4579 6.22136C13.779 7.50509 13.9647 9.06493 13.9976 10.5682C13.791 10.881 13.3655 11.2512 12.8258 11.5746C12.3479 11.8609 11.8511 12.0683 11.4593 12.1584C11.3589 12.0178 11.2306 11.8274 11.1005 11.6283C11.083 11.6014 11.0656 11.5748 11.0486 11.5486C11.8226 11.2697 12.2427 10.8875 12.5161 10.4775C12.6692 10.2477 12.6071 9.93731 12.3774 9.78413C12.1476 9.63095 11.8372 9.69304 11.684 9.9228C11.4284 10.3062 10.7547 11.0002 7.50002 11.0002C4.24536 11.0002 3.57163 10.3062 3.31605 9.92281C3.16288 9.69304 2.85244 9.63095 2.62268 9.78412C2.39291 9.9373 2.33082 10.2477 2.484 10.4775C2.7571 10.8872 3.17667 11.269 3.94943 11.5479Z" fill="#2D2D2D"/>
</g>
<defs>
<clipPath id="clip0_1463_121087">
<rect width="15" height="15" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View file

@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.85355 3.85355C7.04882 3.65829 7.04882 3.34171 6.85355 3.14645C6.65829 2.95118 6.34171 2.95118 6.14645 3.14645L2.14645 7.14645C1.95118 7.34171 1.95118 7.65829 2.14645 7.85355L6.14645 11.8536C6.34171 12.0488 6.65829 12.0488 6.85355 11.8536C7.04882 11.6583 7.04882 11.3417 6.85355 11.1464L3.20711 7.5L6.85355 3.85355ZM12.8536 3.85355C13.0488 3.65829 13.0488 3.34171 12.8536 3.14645C12.6583 2.95118 12.3417 2.95118 12.1464 3.14645L8.14645 7.14645C7.95118 7.34171 7.95118 7.65829 8.14645 7.85355L12.1464 11.8536C12.3417 12.0488 12.6583 12.0488 12.8536 11.8536C13.0488 11.6583 13.0488 11.3417 12.8536 11.1464L9.20711 7.5L12.8536 3.85355Z" fill="#2D2D2D"/>
</svg>

After

Width:  |  Height:  |  Size: 805 B

View file

@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.14645 10.6464C1.95118 10.8417 1.95118 11.1583 2.14645 11.3536C2.34171 11.5488 2.65829 11.5488 2.85355 11.3536L6.85355 7.35355C7.04882 7.15829 7.04882 6.84171 6.85355 6.64645L2.85355 2.64645C2.65829 2.45118 2.34171 2.45118 2.14645 2.64645C1.95118 2.84171 1.95118 3.15829 2.14645 3.35355L5.79289 7L2.14645 10.6464ZM8.14645 10.6464C7.95118 10.8417 7.95118 11.1583 8.14645 11.3536C8.34171 11.5488 8.65829 11.5488 8.85355 11.3536L12.8536 7.35355C13.0488 7.15829 13.0488 6.84171 12.8536 6.64645L8.85355 2.64645C8.65829 2.45118 8.34171 2.45118 8.14645 2.64645C7.95118 2.84171 7.95118 3.15829 8.14645 3.35355L11.7929 7L8.14645 10.6464Z" fill="#2D2D2D"/>
</svg>

After

Width:  |  Height:  |  Size: 801 B

View file

@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.1464 1.14645C11.3417 0.951184 11.6583 0.951184 11.8535 1.14645L13.8535 3.14645C14.0488 3.34171 14.0488 3.65829 13.8535 3.85355L9.91091 7.79618C9.83491 7.87218 9.74715 7.93543 9.65101 7.9835L5.72359 9.94721C5.53109 10.0435 5.29861 10.0057 5.14643 9.85355C4.99425 9.70137 4.95652 9.46889 5.05277 9.27639L7.01648 5.34897C7.06455 5.25283 7.1278 5.16507 7.2038 5.08907L11.1464 1.14645ZM11.5 2.20711L7.91091 5.79618L6.87266 7.87267L7.12731 8.12732L9.2038 7.08907L12.7929 3.5L11.5 2.20711ZM8.99998 2L7.99998 3H3.9C3.47171 3 3.18056 3.00039 2.95552 3.01877C2.73631 3.03668 2.62421 3.06915 2.54601 3.10899C2.35785 3.20487 2.20487 3.35785 2.10899 3.54601C2.06915 3.62421 2.03669 3.73631 2.01878 3.95552C2.00039 4.18056 2 4.47171 2 4.9V11.1C2 11.5283 2.00039 11.8194 2.01878 12.0445C2.03669 12.2637 2.06915 12.3758 2.10899 12.454C2.20487 12.6422 2.35785 12.7951 2.54601 12.891C2.62421 12.9309 2.73631 12.9633 2.95552 12.9812C3.18056 12.9996 3.47171 13 3.9 13H10.1C10.5283 13 10.8194 12.9996 11.0445 12.9812C11.2637 12.9633 11.3758 12.9309 11.454 12.891C11.6422 12.7951 11.7951 12.6422 11.891 12.454C11.9309 12.3758 11.9633 12.2637 11.9812 12.0445C11.9996 11.8194 12 11.5283 12 11.1V6.99998L13 5.99998V11.1V11.1207C13 11.5231 13 11.8553 12.9779 12.1259C12.9549 12.407 12.9057 12.6653 12.782 12.908C12.5903 13.2843 12.2843 13.5903 11.908 13.782C11.6653 13.9057 11.407 13.9549 11.1259 13.9779C10.8553 14 10.5231 14 10.1207 14H10.1H3.9H3.87934C3.47686 14 3.14468 14 2.87409 13.9779C2.59304 13.9549 2.33469 13.9057 2.09202 13.782C1.7157 13.5903 1.40973 13.2843 1.21799 12.908C1.09434 12.6653 1.04506 12.407 1.0221 12.1259C0.99999 11.8553 0.999995 11.5231 1 11.1207V11.1206V11.1V4.9V4.87935V4.87932V4.87931C0.999995 4.47685 0.99999 4.14468 1.0221 3.87409C1.04506 3.59304 1.09434 3.33469 1.21799 3.09202C1.40973 2.71569 1.7157 2.40973 2.09202 2.21799C2.33469 2.09434 2.59304 2.04506 2.87409 2.0221C3.14468 1.99999 3.47685 1.99999 3.87932 2H3.87935H3.9H8.99998Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.49933 0.249978C3.49635 0.249978 0.25 3.49591 0.25 7.50022C0.25 10.703 2.32715 13.4206 5.2081 14.3797C5.57084 14.446 5.70302 14.2221 5.70302 14.0299C5.70302 13.8576 5.69679 13.4019 5.69323 12.797C3.67661 13.235 3.25112 11.825 3.25112 11.825C2.92132 10.9874 2.44599 10.7644 2.44599 10.7644C1.78773 10.3148 2.49584 10.3238 2.49584 10.3238C3.22353 10.3749 3.60629 11.071 3.60629 11.071C4.25298 12.1788 5.30335 11.8588 5.71638 11.6732C5.78225 11.205 5.96962 10.8854 6.17658 10.7043C4.56675 10.5209 2.87415 9.89915 2.87415 7.12102C2.87415 6.32923 3.15677 5.68254 3.62053 5.17561C3.54576 4.99224 3.29697 4.25518 3.69174 3.25689C3.69174 3.25689 4.30015 3.06194 5.68522 3.99971C6.26337 3.83904 6.8838 3.75893 7.50022 3.75581C8.1162 3.75893 8.73619 3.83904 9.31523 3.99971C10.6994 3.06194 11.3069 3.25689 11.3069 3.25689C11.7026 4.25518 11.4538 4.99224 11.3795 5.17561C11.8441 5.68254 12.1245 6.32923 12.1245 7.12102C12.1245 9.90628 10.4292 10.5191 8.81452 10.6985C9.07444 10.9224 9.30633 11.3648 9.30633 12.0413C9.30633 13.0102 9.29742 13.7922 9.29742 14.0299C9.29742 14.2239 9.42828 14.4496 9.79591 14.3788C12.6746 13.4179 14.75 10.7025 14.75 7.50022C14.75 3.49591 11.5036 0.249978 7.49933 0.249978" fill="#2D2D2D"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,10 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1464_121177)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.5 0C7.77614 0 8 0.223858 8 0.5V2.5C8 2.77614 7.77614 3 7.5 3C7.22386 3 7 2.77614 7 2.5V0.5C7 0.223858 7.22386 0 7.5 0ZM2.1967 2.1967C2.39196 2.00144 2.70854 2.00144 2.90381 2.1967L4.31802 3.61091C4.51328 3.80617 4.51328 4.12276 4.31802 4.31802C4.12276 4.51328 3.80617 4.51328 3.61091 4.31802L2.1967 2.90381C2.00144 2.70854 2.00144 2.39196 2.1967 2.1967ZM0.5 7C0.223858 7 0 7.22386 0 7.5C0 7.77614 0.223858 8 0.5 8H2.5C2.77614 8 3 7.77614 3 7.5C3 7.22386 2.77614 7 2.5 7H0.5ZM2.1967 12.8033C2.00144 12.608 2.00144 12.2915 2.1967 12.0962L3.61091 10.682C3.80617 10.4867 4.12276 10.4867 4.31802 10.682C4.51328 10.8772 4.51328 11.1938 4.31802 11.3891L2.90381 12.8033C2.70854 12.9986 2.39196 12.9986 2.1967 12.8033ZM12.5 7C12.2239 7 12 7.22386 12 7.5C12 7.77614 12.2239 8 12.5 8H14.5C14.7761 8 15 7.77614 15 7.5C15 7.22386 14.7761 7 14.5 7H12.5ZM10.682 4.31802C10.4867 4.12276 10.4867 3.80617 10.682 3.61091L12.0962 2.1967C12.2915 2.00144 12.608 2.00144 12.8033 2.1967C12.9986 2.39196 12.9986 2.70854 12.8033 2.90381L11.3891 4.31802C11.1938 4.51328 10.8772 4.51328 10.682 4.31802ZM8 12.5C8 12.2239 7.77614 12 7.5 12C7.22386 12 7 12.2239 7 12.5V14.5C7 14.7761 7.22386 15 7.5 15C7.77614 15 8 14.7761 8 14.5V12.5ZM10.682 10.682C10.8772 10.4867 11.1938 10.4867 11.3891 10.682L12.8033 12.0962C12.9986 12.2915 12.9986 12.608 12.8033 12.8033C12.608 12.9986 12.2915 12.9986 12.0962 12.8033L10.682 11.3891C10.4867 11.1938 10.4867 10.8772 10.682 10.682ZM5.5 7.5C5.5 6.39543 6.39543 5.5 7.5 5.5C8.60457 5.5 9.5 6.39543 9.5 7.5C9.5 8.60457 8.60457 9.5 7.5 9.5C6.39543 9.5 5.5 8.60457 5.5 7.5ZM7.5 4.5C5.84315 4.5 4.5 5.84315 4.5 7.5C4.5 9.15685 5.84315 10.5 7.5 10.5C9.15685 10.5 10.5 9.15685 10.5 7.5C10.5 5.84315 9.15685 4.5 7.5 4.5Z" fill="#2D2D2D"/>
</g>
<defs>
<clipPath id="clip0_1464_121177">
<rect width="15" height="15" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 3C1.22386 3 1 3.22386 1 3.5C1 3.77614 1.22386 4 1.5 4H13.5C13.7761 4 14 3.77614 14 3.5C14 3.22386 13.7761 3 13.5 3H1.5ZM1 7.5C1 7.22386 1.22386 7 1.5 7H13.5C13.7761 7 14 7.22386 14 7.5C14 7.77614 13.7761 8 13.5 8H1.5C1.22386 8 1 7.77614 1 7.5ZM1 11.5C1 11.2239 1.22386 11 1.5 11H13.5C13.7761 11 14 11.2239 14 11.5C14 11.7761 13.7761 12 13.5 12H1.5C1.22386 12 1 11.7761 1 11.5Z" fill="#2D2D2D"/>
</svg>

After

Width:  |  Height:  |  Size: 552 B

View file

@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 6.5C10 8.433 8.433 10 6.5 10C4.567 10 3 8.433 3 6.5C3 4.567 4.567 3 6.5 3C8.433 3 10 4.567 10 6.5ZM9.30884 10.0159C8.53901 10.6318 7.56251 11 6.5 11C4.01472 11 2 8.98528 2 6.5C2 4.01472 4.01472 2 6.5 2C8.98528 2 11 4.01472 11 6.5C11 7.56251 10.6318 8.53901 10.0159 9.30884L12.8536 12.1464C13.0488 12.3417 13.0488 12.6583 12.8536 12.8536C12.6583 13.0488 12.3417 13.0488 12.1464 12.8536L9.30884 10.0159Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 574 B

View file

@ -0,0 +1,4 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.23347 4.69633C7.23347 2.96887 8.63346 1.5686 10.3601 1.5686C12.3031 1.5686 13.4957 3.33048 13.4957 5.00083C13.4957 8.68713 10.6874 12.9746 5.52134 12.9746C3.93917 12.9746 2.46555 12.5111 1.22516 11.7152C1.02917 11.5895 0.945933 11.3451 1.02444 11.1258C1.10294 10.9066 1.3224 10.7706 1.55367 10.7978C1.74004 10.8198 1.92971 10.831 2.12221 10.831C2.82595 10.831 3.49398 10.6795 4.09587 10.4071C3.31987 10.0395 2.71968 9.36178 2.45469 8.53509C2.40426 8.37777 2.43497 8.20646 2.53511 8.07684C1.90532 7.50479 1.51 6.67941 1.51 5.76151V5.72768C1.51 5.55051 1.60376 5.38656 1.75646 5.29673C1.78736 5.27855 1.81973 5.26396 1.85301 5.25295C1.64719 4.83575 1.53143 4.366 1.53143 3.86966C1.53143 3.29809 1.68595 2.76085 1.95411 2.29827C2.03647 2.1562 2.18333 2.06363 2.34703 2.05061C2.51073 2.03759 2.67039 2.10578 2.77417 2.23305C3.86547 3.57129 5.44077 4.49586 7.23377 4.73964C7.23357 4.72523 7.23347 4.71079 7.23347 4.69633ZM2.63816 6.49033C2.88893 7.17798 3.48434 7.70032 4.21566 7.84714C4.44315 7.8928 4.60933 8.08897 4.61698 8.32087C4.62463 8.55276 4.47175 8.75946 4.24777 8.82002C4.09338 8.86177 3.93479 8.89191 3.77291 8.90972C4.15542 9.38625 4.73894 9.6946 5.3937 9.70616C5.60531 9.7099 5.79165 9.84646 5.85893 10.0471C5.92622 10.2478 5.85988 10.4691 5.69329 10.5996C5.05138 11.1027 4.3004 11.4734 3.48222 11.6703C4.12699 11.8681 4.81144 11.9746 5.52134 11.9746C10.0203 11.9746 12.4957 8.25712 12.4957 5.00083C12.4957 3.71212 11.5944 2.5686 10.3601 2.5686C9.18603 2.5686 8.23347 3.52088 8.23347 4.69633C8.23347 4.86287 8.25266 5.0252 8.28862 5.18287C8.32343 5.33551 8.28474 5.49565 8.18406 5.61556C8.08339 5.73546 7.93236 5.80129 7.776 5.79342C5.74502 5.69116 3.91635 4.83008 2.5654 3.48932C2.54308 3.61278 2.53143 3.73987 2.53143 3.86966C2.53143 4.60737 2.90661 5.25897 3.47769 5.64076C3.66328 5.76483 3.74413 5.9969 3.67582 6.20943C3.6075 6.42196 3.40657 6.56346 3.18345 6.55616C2.99723 6.55006 2.81498 6.52765 2.63816 6.49033Z" fill="#2D2D2D"/>
<path d="M13.1965 3.56882C13.7282 3.5051 14.234 3.36413 14.7049 3.15549L14.7038 3.15718C14.3525 3.68329 13.907 4.14511 13.3939 4.51615L12.6833 2.75231C13.2816 2.63502 13.8438 2.41623 14.3513 2.11511C14.1551 2.72862 13.7389 3.24402 13.1965 3.56882Z" fill="#2D2D2D"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

View file

@ -0,0 +1,9 @@
<svg width="71" height="18" viewBox="0 0 71 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 1.97802C0 0.88559 0.848303 0 1.89474 0H16.1053C17.1517 0 18 0.885591 18 1.97802V16.022C18 17.1144 17.1517 18 16.1053 18H1.89474C0.848303 18 0 17.1144 0 16.022V1.97802ZM10.6264 5.14801C10.6264 5.58302 10.4777 5.95212 10.1803 6.25531C9.88296 6.5585 9.52095 6.71009 9.0943 6.71009C8.65471 6.71009 8.28624 6.5585 7.98888 6.25531C7.69151 5.95212 7.54283 5.58302 7.54283 5.14801C7.54283 4.71301 7.69151 4.34391 7.98888 4.04072C8.28624 3.73753 8.65471 3.58594 9.0943 3.58594C9.52095 3.58594 9.88296 3.73753 10.1803 4.04072C10.4777 4.34391 10.6264 4.71301 10.6264 5.14801ZM7.52344 10.6224C7.52344 10.1874 7.67212 9.81831 7.96948 9.51512C8.27978 9.19875 8.65471 9.04056 9.0943 9.04056C9.50802 9.04056 9.87003 9.19875 10.1803 9.51512C10.4906 9.81831 10.6716 10.161 10.7233 10.5433C10.8268 11.2552 10.6975 11.9604 10.3355 12.659C9.98639 13.3577 9.48216 13.8916 8.82279 14.2607C8.46078 14.4716 8.16342 14.465 7.9307 14.2409C7.71091 14.03 7.77555 13.7795 8.12463 13.4895C8.31856 13.3445 8.48017 13.16 8.60946 12.9359C8.73875 12.7118 8.82279 12.4811 8.86158 12.2438C8.87451 12.1384 8.82925 12.0856 8.72582 12.0856C8.46725 12.0724 8.2022 11.9274 7.9307 11.6506C7.65919 11.3738 7.52344 11.0311 7.52344 10.6224Z" fill="#000000" fill-opacity="1"/>
<path d="M34.156 14.5761C34.2636 14.5761 34.3174 14.5223 34.3174 14.4147V12.2234C34.3174 12.0073 34.1422 11.8321 33.9261 11.8321H33.6424C33.5543 11.8321 33.4859 11.8076 33.437 11.7587C33.3978 11.7196 33.3783 11.6609 33.3783 11.5826V3.73207C33.3783 3.62446 33.3245 3.57065 33.2168 3.57065H30.7467C30.5306 3.57065 30.3554 3.74585 30.3554 3.96196V12.463C30.3554 13.1674 30.556 13.6957 30.9571 14.0478C31.3582 14.4 31.8717 14.5761 32.4978 14.5761H34.156Z" fill="#000000" fill-opacity="1"/>
<path d="M29.0935 12.2087C29.0935 11.9926 28.9183 11.8174 28.7022 11.8174H27.6701C27.3375 11.8174 27.1125 11.7587 26.9951 11.6413C26.8484 11.5043 26.775 11.2842 26.775 10.981V9.68478C26.775 9.46867 26.9502 9.29348 27.1663 9.29348H28.7022C28.9183 9.29348 29.0935 9.11829 29.0935 8.90217V6.71087C29.0935 6.60326 29.0397 6.54946 28.9321 6.54946H27.1663C26.9502 6.54946 26.775 6.37426 26.775 6.15815V4.46576C26.775 4.35815 26.7212 4.30435 26.6136 4.30435H24.2315C24.0154 4.30435 23.8402 4.47954 23.8402 4.69565V6.15815C23.8402 6.37426 23.665 6.54946 23.4489 6.54946H23.087C22.8708 6.54946 22.6957 6.72465 22.6957 6.94076V9.13207C22.6957 9.23967 22.7495 9.29348 22.8571 9.29348H23.4489C23.665 9.29348 23.8402 9.46867 23.8402 9.68478V11.2011C23.8402 12.3359 24.1386 13.1821 24.7353 13.7397C25.3321 14.2973 26.1978 14.5761 27.3326 14.5761H28.9321C29.0397 14.5761 29.0935 14.5223 29.0935 14.4147V12.2087Z" fill="#000000" fill-opacity="1"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M37.6867 6.40272C38.1367 6.40272 38.5231 6.51522 38.8459 6.74022C39.1499 6.94586 39.4008 7.21279 39.5985 7.54101C39.6332 7.59855 39.6947 7.63533 39.7619 7.63533C39.8638 7.63533 39.9464 7.55268 39.9464 7.45074V3.96196C39.9464 3.74584 40.1216 3.57065 40.3377 3.57065H42.7198C42.8274 3.57065 42.8812 3.62446 42.8812 3.73207V14.1848C42.8812 14.4009 42.706 14.5761 42.4899 14.5761H40.1079C40.0002 14.5761 39.9464 14.5223 39.9464 14.4147V13.5333C39.9464 13.4285 39.8614 13.3435 39.7566 13.3435C39.692 13.3435 39.6321 13.3765 39.596 13.4301C39.3462 13.8013 39.0668 14.1051 38.7579 14.3413C38.4252 14.5957 38.0095 14.7228 37.5106 14.7228C36.8356 14.7228 36.3024 14.4929 35.9111 14.0332C35.5198 13.5734 35.3242 12.9571 35.3242 12.1842V9.10272C35.3242 8.26141 35.5247 7.60109 35.9258 7.12174C36.3367 6.64239 36.9236 6.40272 37.6867 6.40272ZM38.4204 11.9348C38.528 12.0522 38.7187 12.1109 38.9926 12.1109C39.2693 12.1109 39.5126 12.0774 39.7224 12.0105C39.8636 11.9655 39.9464 11.8265 39.9464 11.6783V9.35616C39.9464 9.22533 39.8827 9.09961 39.7647 9.04299C39.6723 8.9986 39.5763 8.9647 39.4769 8.94131C39.3106 8.89239 39.1492 8.86793 38.9926 8.86793C38.4937 8.86793 38.2443 9.11739 38.2443 9.6163V11.3772C38.2443 11.6413 38.303 11.8272 38.4204 11.9348Z" fill="#000000" fill-opacity="1"/>
<path d="M47.1839 14.5761C47.4001 14.5761 47.5752 14.4009 47.5752 14.1848V10.0559C47.5752 9.91955 47.6451 9.79072 47.7678 9.73131C47.8883 9.67299 48.0148 9.62487 48.1475 9.58696C48.353 9.51848 48.5829 9.48424 48.8372 9.48424C49.0231 9.48424 49.1992 9.49891 49.3655 9.52826C49.5291 9.55553 49.6884 9.59123 49.8436 9.63539C49.8671 9.64209 49.8915 9.64565 49.916 9.64565C50.0577 9.64565 50.1725 9.5308 50.1725 9.38912V6.71087C50.1725 6.64239 50.1481 6.5837 50.0992 6.53478C50.0698 6.50544 50.016 6.47609 49.9378 6.44674C49.8595 6.41739 49.7421 6.40272 49.5856 6.40272C49.1258 6.40272 48.7589 6.54457 48.485 6.82826C48.2423 7.07994 48.0533 7.44725 47.918 7.93019C47.8966 8.00665 47.8277 8.06087 47.7483 8.06087C47.6527 8.06087 47.5752 7.9834 47.5752 7.88783V6.71087C47.5752 6.60326 47.5214 6.54946 47.4138 6.54946H44.9878C44.7716 6.54946 44.5964 6.72465 44.5964 6.94076V14.4147C44.5964 14.5223 44.6502 14.5761 44.7579 14.5761H47.1839Z" fill="#000000" fill-opacity="1"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M58.368 9.41087V11.788C58.368 11.9085 58.4096 11.9874 58.4929 12.0246C58.5165 12.0351 58.5407 12.0441 58.5651 12.0526C58.5923 12.0621 58.6196 12.0668 58.6468 12.0668H58.6664C58.8825 12.0668 59.0577 12.242 59.0577 12.4582V14.4147C59.0577 14.5223 59.0039 14.5761 58.8963 14.5761H57.9131C57.4338 14.5761 56.9936 14.4783 56.5925 14.2826C56.2443 14.1085 55.9774 13.857 55.7919 13.528C55.7494 13.4526 55.6716 13.4022 55.585 13.4022C55.5009 13.4022 55.4244 13.4497 55.3826 13.5227C55.1746 13.8859 54.9029 14.1735 54.5675 14.3853C54.2055 14.6103 53.7408 14.7228 53.1734 14.7228C52.4887 14.7228 51.9457 14.537 51.5446 14.1652C51.1436 13.7935 50.943 13.2897 50.943 12.6538V12.0815C50.943 11.3967 51.1436 10.8587 51.5446 10.4674C51.9555 10.0761 52.5327 9.88044 53.2762 9.88044H55.3305C55.444 9.88044 55.5359 9.78846 55.5359 9.675C55.5359 9.33261 55.443 9.10272 55.2571 8.98533C55.0811 8.85815 54.7387 8.79457 54.23 8.79457C53.3815 8.79457 52.5424 8.91812 51.7129 9.16522C51.6889 9.17237 51.664 9.17609 51.6389 9.17609C51.4977 9.17609 51.3832 9.06162 51.3832 8.92041V7.0337C51.3832 6.96522 51.3979 6.9163 51.4273 6.88696C51.4957 6.81848 51.6816 6.74511 51.9849 6.66685C52.2979 6.58859 52.6745 6.52011 53.1148 6.46141C53.5647 6.39294 54.0245 6.3587 54.4941 6.3587C55.8343 6.3587 56.8126 6.60326 57.4289 7.09239C58.055 7.57174 58.368 8.34457 58.368 9.41087ZM54.1566 12.6098C54.5823 12.6098 54.9796 12.5136 55.3484 12.3213C55.4677 12.259 55.5359 12.1319 55.5359 11.9972V11.6951C55.5359 11.479 55.3608 11.3038 55.1446 11.3038H54.1859C54.0196 11.3038 53.8925 11.3478 53.8044 11.4359C53.7262 11.5337 53.687 11.6511 53.687 11.788V12.1402C53.687 12.4533 53.8436 12.6098 54.1566 12.6098Z" fill="#000000" fill-opacity="1"/>
<path d="M64.2831 6.54946C64.1006 6.54946 63.9423 6.67566 63.9017 6.85362C63.6938 7.76648 63.5017 8.62378 63.3255 9.42554C63.1518 10.2575 63.0208 11.0466 62.9326 11.7931C62.922 11.8821 62.8468 11.9495 62.7573 11.9495C62.6585 11.9495 62.5792 11.8681 62.5807 11.7694C62.5921 11.0174 62.5616 10.2655 62.4891 9.51359C62.4206 8.70163 62.3326 7.79674 62.225 6.79891C62.2152 6.69131 62.1956 6.62283 62.1663 6.59348C62.1369 6.56413 62.0929 6.54946 62.0342 6.54946H59.9316C59.6887 6.54946 59.5045 6.76857 59.5457 7.008C59.7421 8.14646 59.9308 9.28491 60.1119 10.4234C60.3271 11.7147 60.5424 12.9571 60.7576 14.1505C60.7911 14.3014 60.8282 14.4092 60.8689 14.4739C60.8815 14.4939 60.8984 14.5108 60.918 14.5242C60.9687 14.5588 61.0228 14.5761 61.0804 14.5761H63.6985C63.8764 14.5761 64.032 14.456 64.0769 14.2839C64.1528 13.9935 64.2202 13.7339 64.2793 13.5049C64.3576 13.2016 64.426 12.9033 64.4847 12.6098C64.5434 12.3163 64.6021 11.9935 64.6608 11.6413C64.7227 11.3145 64.7885 10.9198 64.8583 10.4574C64.8714 10.3704 64.9461 10.306 65.034 10.306C65.1228 10.306 65.1979 10.3717 65.2098 10.4597C65.2695 10.902 65.3213 11.2812 65.3652 11.5973C65.4239 11.9397 65.4777 12.2429 65.5266 12.5071C65.5853 12.7614 65.644 13.0207 65.7027 13.2848C65.7614 13.5391 65.8299 13.8326 65.9081 14.1652C65.95 14.3076 65.9919 14.4106 66.0338 14.4741C66.0468 14.4939 66.0637 14.5108 66.0832 14.5242C66.1339 14.5588 66.188 14.5761 66.2456 14.5761H68.9092C69.0998 14.5761 69.2627 14.4388 69.2949 14.251L69.9141 10.6435C70.1391 9.33261 70.3592 8.03152 70.5744 6.74022C70.5842 6.61304 70.5402 6.54946 70.4424 6.54946H68.1855C67.9841 6.54946 67.8155 6.70245 67.7962 6.90299C67.712 7.77975 67.6353 8.61081 67.5663 9.3962C67.5027 10.2129 67.4729 11.0044 67.4767 11.7706C67.4772 11.8691 67.3978 11.9495 67.2993 11.9495C67.2091 11.9495 67.1334 11.8815 67.123 11.7918C67.0341 11.0294 66.8982 10.2455 66.7152 9.44022C66.5293 8.57935 66.3239 7.66957 66.0989 6.71087C66.0695 6.60326 66.0108 6.54946 65.9228 6.54946H64.2831Z" fill="#000000" fill-opacity="1"/>
</svg>

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View file

@ -0,0 +1,317 @@
import { ApiModel } from '@microsoft/api-extractor-model'
import fs from 'fs'
import matter from 'gray-matter'
import path from 'path'
import authors from '../content/authors.json'
import sections from '../content/sections.json'
import {
Article,
Articles,
Category,
GeneratedContent,
Group,
MarkdownContent,
Section,
Status,
} from '../types/content-types'
import { getApiMarkdown } from './getApiMarkdown'
import { getSlug } from './utils'
const { log } = console
type InputCategory = {
id: string
title: string
description: string
groups: Group[]
}
type InputSection = {
id: string
title: string
description: string
categories: InputCategory[]
}
function generateSection(
section: InputSection,
content: MarkdownContent,
articles: Articles
): Section {
// A temporary table of categories
const _categories: Record<string, Category> = {}
// Uncategorized articles
const _ucg: Article[] = []
// A temporary table of articles mapped to categories
const _categoryArticles: Record<string, Article[]> = Object.fromEntries(
section.categories.map((category) => [category.id, []])
)
// The file directory for this section
const dir = path.join(process.cwd(), 'content', section.id)
fs.readdirSync(dir, { withFileTypes: false }).forEach((result: string | Buffer) => {
try {
const filename = result.toString()
const fileContent = fs.readFileSync(path.join(dir, filename)).toString()
const extension = path.extname(filename)
const articleId = filename.replace(extension, '')
const parsed = matter({ content: fileContent }, {})
if (process.env.NODE_ENV !== 'development' && parsed.data.status !== 'published') {
return
}
// If a category was provided but that category was not found in the section, throw an error
const category =
parsed.data.category && section.categories.find((c) => c.id === parsed.data.category)
if (parsed.data.category && !category) {
throw Error(
`Could not find a category for section ${section.id} with id ${parsed.data.category}.`
)
}
if (parsed.data.author && !authors[parsed.data.author as keyof typeof authors]) {
throw Error(`Could not find an author with id ${parsed.data.author}.`)
}
// By default, the category is ucg (uncategorized)
const { category: categoryId = 'ucg' } = parsed.data
const article: Article = {
id: articleId,
sectionIndex: 0,
groupIndex: -1,
groupId: parsed.data.group ?? null,
categoryIndex: parsed.data.order ?? -1,
sectionId: section.id,
categoryId: parsed.data.category ?? 'ucg',
status: parsed.data.status ?? Status.Draft,
title: parsed.data.title ?? 'Article',
description: parsed.data.description ?? 'An article for the docs site.',
hero: parsed.data.hero ?? null,
date: parsed.data.date ? new Date(parsed.data.date).toISOString() : null,
keywords: parsed.data.keywords ?? [],
next: null,
prev: null,
author: parsed.data.author
? authors[parsed.data.author as keyof typeof authors] ?? null
: null,
sourceUrl: `https://github.com/tldraw/tldraw-lite/tree/main/apps/docs/content/${section.id}/${articleId}${extension}`,
}
if (article.id === section.id) {
article.categoryIndex = -1
article.sectionIndex = -1
articles[section.id + '_index'] = article
content[section.id + '_index'] = parsed.content
} else {
if (category) {
if (article.id === category.id) {
article.categoryIndex = -1
article.sectionIndex = -1
articles[category.id + '_index'] = article
content[category.id + '_index'] = parsed.content
} else {
_categoryArticles[categoryId].push(article)
content[articleId] = parsed.content
}
} else {
_ucg.push(article)
content[articleId] = parsed.content
}
}
} catch (e) {
console.error(e)
}
})
const sortArticles = (articleA: Article, articleB: Article) => {
const { categoryIndex: categoryIndexA, date: dateA = '01/01/1970' } = articleA
const { categoryIndex: categoryIndexB, date: dateB = '01/01/1970' } = articleB
return categoryIndexA === categoryIndexB
? new Date(dateB!).getTime() > new Date(dateA!).getTime()
? 1
: -1
: categoryIndexA < categoryIndexB
? -1
: 1
}
let sectionIndex = 0
// Sort ucg articles by date and add them to the articles table
_ucg.sort(sortArticles).forEach((article, i) => {
article.categoryIndex = i
article.sectionIndex = sectionIndex++
article.prev = _ucg[i - 1]?.id ?? null
article.next = _ucg[i + 1]?.id ?? null
articles[article.id] = article
})
// Sort categorized articles by date and add them to the articles table
section.categories.forEach((category) => {
const categoryArticles = _categoryArticles[category.id]
categoryArticles.sort(sortArticles).forEach((article, i) => {
article.categoryIndex = i
article.sectionIndex = sectionIndex++
article.prev = categoryArticles[i - 1]?.id ?? null
article.next = categoryArticles[i + 1]?.id ?? null
articles[article.id] = article
})
_categories[category.id] = {
...category,
articleIds: categoryArticles.map((article) => article.id),
}
})
return {
...section,
categories: [
{
id: 'ucg',
title: 'Uncategorized',
description: 'Articles that do not belong to a category.',
groups: [],
articleIds: _ucg
.sort((a, b) => a.sectionIndex - b.sectionIndex)
.map((article) => article.id),
},
...section.categories.map(({ id }) => _categories[id]).filter((c) => c.articleIds.length > 0),
],
}
}
async function generateApiDocs() {
const apiInputSection: InputSection = {
id: 'gen' as string,
title: 'API',
description: "Reference for the tldraw package's APIs (generated).",
categories: [],
}
const addedCategories = new Set<string>()
const OUTPUT_DIR = path.join(process.cwd(), 'content', 'gen')
if (fs.existsSync(OUTPUT_DIR)) {
fs.rmdirSync(OUTPUT_DIR, { recursive: true })
}
fs.mkdirSync(OUTPUT_DIR)
// to include more packages in docs, add them to devDependencies in package.json
const packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8'))
const tldrawPackagesToIncludeInDocs = Object.keys(packageJson.devDependencies).filter((dep) =>
dep.startsWith('@tldraw/')
)
const model = new ApiModel()
for (const packageName of tldrawPackagesToIncludeInDocs) {
// Get the file contents
const filePath = path.join(
process.cwd(),
'..',
'..',
'packages',
packageName.replace('@tldraw/', ''),
'api',
'api.json'
)
const packageModel = model.loadPackage(filePath)
const categoryName = packageModel.name.replace(`@tldraw/`, '')
if (!addedCategories.has(categoryName)) {
apiInputSection.categories!.push({
id: categoryName,
title: packageModel.name,
description: '',
groups: [
{
id: 'Namespace',
title: 'Namespaces',
},
{
id: 'Class',
title: 'Classes',
},
{
id: 'Function',
title: 'Functions',
},
{
id: 'Variable',
title: 'Variables',
},
{
id: 'Enum',
title: 'Enums',
},
{
id: 'Interface',
title: 'Interfaces',
},
{
id: 'TypeAlias',
title: 'TypeAliases',
},
],
})
addedCategories.add(categoryName)
}
const entrypoint = packageModel.entryPoints[0]
for (let j = 0; j < entrypoint.members.length; j++) {
const item = entrypoint.members[j]
const result = await getApiMarkdown(categoryName, item, j)
const outputFileName = `${getSlug(item)}.mdx`
fs.writeFileSync(path.join(OUTPUT_DIR, outputFileName), result.markdown)
}
}
return apiInputSection
}
export async function generateContent(): Promise<GeneratedContent> {
const content: MarkdownContent = {}
const articles: Articles = {}
const apiSection = await generateApiDocs()
log('• Generating site content (content.json)')
try {
const outputSections: Section[] = [...(sections as InputSection[]), apiSection]
.map((section) => generateSection(section, content, articles))
.filter((section) => section.categories.some((c) => c.articleIds.length > 0))
log('✔ Generated site content.')
// Write to disk
const contentComplete = { sections: outputSections, content, articles }
fs.writeFileSync(
path.join(process.cwd(), 'content.json'),
JSON.stringify(contentComplete, null, 2)
)
return contentComplete
} catch (error) {
log(`x Could not generate site content.`)
throw error
}
}

View file

@ -0,0 +1,341 @@
import {
ApiClass,
ApiConstructSignature,
ApiConstructor,
ApiDeclaredItem,
ApiDocumentedItem,
ApiEnum,
ApiFunction,
ApiInterface,
ApiItem,
ApiItemKind,
ApiMethod,
ApiMethodSignature,
ApiNamespace,
ApiProperty,
ApiPropertySignature,
ApiReadonlyMixin,
ApiReleaseTagMixin,
ApiStaticMixin,
ApiTypeAlias,
ApiVariable,
Excerpt,
ReleaseTag,
} from '@microsoft/api-extractor-model'
import { assert, exhaustiveSwitchError } from '@tldraw/utils'
import { MarkdownWriter, formatWithPrettier, getPath, getSlug } from './utils'
type Result = { markdown: string }
const date = new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
}).format(new Date())
export async function getApiMarkdown(categoryName: string, item: ApiItem, j: number) {
const result = { markdown: '' }
addFrontmatter(result, item, categoryName, j)
addTags(result, item)
const toc: Result = { markdown: '' }
const membersResult: Result = { markdown: '' }
if (item.members) {
const constructors = []
const properties = []
const methods = []
for (const member of item.members) {
switch (member.kind) {
case ApiItemKind.Constructor:
case ApiItemKind.ConstructSignature:
constructors.push(member)
break
case ApiItemKind.Variable:
case ApiItemKind.Property:
case ApiItemKind.PropertySignature:
properties.push(member)
break
case ApiItemKind.Method:
case ApiItemKind.Function:
case ApiItemKind.MethodSignature:
methods.push(member)
break
case ApiItemKind.EnumMember:
case ApiItemKind.Class:
case ApiItemKind.TypeAlias:
case ApiItemKind.Interface:
// TODO: document these
break
default:
throw new Error(`Unknown member kind: ${member.kind} ${member.displayName}`)
}
}
const constructorResult = { markdown: '' }
const propertiesResult = { markdown: '' }
const methodsResult = { markdown: '' }
if (constructors.length) {
for (const member of constructors) {
await addMarkdownForMember(constructorResult, member)
addHorizontalRule(constructorResult)
}
addMarkdown(membersResult, constructorResult.markdown)
}
if (properties.length) {
addMarkdown(toc, `- [Properties](#properties)\n`)
addMarkdown(propertiesResult, `## Properties\n\n`)
for (const member of properties) {
addMarkdown(toc, ` - [${member.displayName}](#${getSlug(member)})\n`)
await addMarkdownForMember(propertiesResult, member)
addHorizontalRule(propertiesResult)
}
addMarkdown(membersResult, propertiesResult.markdown)
}
if (methods.length) {
addMarkdown(toc, `- [Methods](#methods)\n`)
addMarkdown(methodsResult, `## Methods\n\n`)
for (const member of methods) {
addMarkdown(toc, ` - [${member.displayName}](#${getSlug(member)})\n`)
await addMarkdownForMember(methodsResult, member)
addHorizontalRule(methodsResult)
}
addMarkdown(membersResult, methodsResult.markdown)
}
}
if (toc.markdown.length) {
result.markdown += `<details>\n\t<summary>Table of Contents</summary>\n`
addMarkdown(result, toc.markdown)
result.markdown += `</details>\n\n`
}
await addDocComment(result, item)
addReferences(result, item)
if (membersResult.markdown.length) {
addHorizontalRule(result)
addMarkdown(result, membersResult.markdown)
}
return result
}
/* --------------------- Helpers -------------------- */
function addMarkdown(result: Result, markdown: string) {
result.markdown += markdown
}
async function addMarkdownForMember(result: Result, member: ApiItem) {
addMemberName(result, member)
addTags(result, member)
await addDocComment(result, member)
addReferences(result, member)
}
function addFrontmatter(result: Result, member: ApiItem, categoryName: string, order: number) {
result.markdown += `---
title: ${member.displayName}
status: published
category: ${categoryName}
group: ${member.kind}
author: api
date: ${date}
order: ${order}
---`
}
function addHorizontalRule(result: Result) {
result.markdown += `---\n\n`
}
function addMemberName(result: Result, member: ApiItem) {
if (member.kind === 'Constructor') {
result.markdown += `### \`Constructor\`\n\n`
return
}
if (!member.displayName) return
result.markdown += `### \`${member.displayName}${
member.kind === 'Method' ? '()' : ''
}\` \\{#${getSlug(member)}}\n\n`
}
async function addDocComment(result: Result, member: ApiItem) {
if (!(member instanceof ApiDocumentedItem)) {
return
}
if (member.tsdocComment) {
result.markdown += await MarkdownWriter.docNodeToMarkdown(member.tsdocComment.summarySection)
const exampleBlocks = member.tsdocComment.customBlocks.filter(
(block) => block.blockTag.tagNameWithUpperCase === '@EXAMPLE'
)
if (exampleBlocks.length) {
result.markdown += `\n\n`
result.markdown += `##### Example\n\n`
for (const example of exampleBlocks) {
result.markdown += await MarkdownWriter.docNodeToMarkdown(example.content)
}
}
}
if (
member instanceof ApiMethod ||
member instanceof ApiMethodSignature ||
member instanceof ApiConstructor ||
member instanceof ApiConstructSignature ||
member instanceof ApiFunction
) {
result.markdown += `##### Parameters\n\n\n`
if (!member.parameters.length) {
result.markdown += `None\n\n`
} else {
result.markdown += '<ParametersTable>\n\n'
for (const param of member.parameters) {
result.markdown += '<ParametersTableRow>\n'
result.markdown += '<ParametersTableName>\n\n'
result.markdown += `\`${param.name}\`\n\n`
if (param.isOptional) {
result.markdown += ` <Small>(optional)</Small>\n\n`
}
result.markdown += `</ParametersTableName>\n`
result.markdown += `<ParametersTableDescription>\n\n`
result.markdown += await typeExcerptToMarkdown(param.parameterTypeExcerpt, {
kind: 'ParameterType',
printWidth: 60,
})
result.markdown += `\n\n`
if (param.tsdocParamBlock) {
result.markdown += await MarkdownWriter.docNodeToMarkdown(param.tsdocParamBlock.content)
}
result.markdown += `\n\n</ParametersTableDescription>\n`
result.markdown += `</ParametersTableRow>\n`
}
result.markdown += '</ParametersTable>\n\n'
}
if (!(member instanceof ApiConstructor)) {
result.markdown += `##### Returns\n\n\n`
result.markdown += await typeExcerptToMarkdown(member.returnTypeExcerpt, {
kind: 'ReturnType',
})
result.markdown += `\n\n`
if (member.tsdocComment && member.tsdocComment.returnsBlock) {
result.markdown += await MarkdownWriter.docNodeToMarkdown(
member.tsdocComment.returnsBlock.content
)
}
}
} else if (
member instanceof ApiVariable ||
member instanceof ApiTypeAlias ||
member instanceof ApiProperty ||
member instanceof ApiPropertySignature ||
member instanceof ApiClass ||
member instanceof ApiInterface ||
member instanceof ApiEnum ||
member instanceof ApiNamespace
) {
// no specific docs for these types
result.markdown += `##### Signature\n\n\n`
result.markdown += await typeExcerptToMarkdown(member.excerpt, { kind: member.kind })
result.markdown += `\n\n`
} else {
throw new Error('unknown member kind: ' + member.kind)
}
}
async function typeExcerptToMarkdown(
excerpt: Excerpt,
{ kind, printWidth }: { kind: ApiItemKind | 'ReturnType' | 'ParameterType'; printWidth?: number }
) {
let code = ''
for (const token of excerpt.spannedTokens) {
code += token.text
}
code = code.replace(/^export /, '')
code = code.replace(/^declare /, '')
switch (kind) {
case ApiItemKind.CallSignature:
case ApiItemKind.EntryPoint:
case ApiItemKind.EnumMember:
case ApiItemKind.Function:
case ApiItemKind.Model:
case ApiItemKind.Namespace:
case ApiItemKind.None:
case ApiItemKind.Package:
case ApiItemKind.TypeAlias:
code = await formatWithPrettier(code, { printWidth })
break
case 'ReturnType':
case 'ParameterType':
code = await formatWithPrettier(`type X = () =>${code}`, { printWidth })
assert(code.startsWith('type X = () =>'))
code = code = code.replace(/^type X = \(\) =>[ \n]/, '')
break
case ApiItemKind.Class:
case ApiItemKind.Enum:
case ApiItemKind.Interface:
code = await formatWithPrettier(`${code} {}`, { printWidth })
break
case ApiItemKind.Constructor:
case ApiItemKind.ConstructSignature:
case ApiItemKind.IndexSignature:
case ApiItemKind.Method:
case ApiItemKind.MethodSignature:
case ApiItemKind.Property:
case ApiItemKind.PropertySignature:
case ApiItemKind.Variable:
code = await formatWithPrettier(`class X { ${code} }`, { printWidth })
assert(code.startsWith('class X {\n'))
assert(code.endsWith('\n}'))
code = code.slice('class X {\n'.length, -'\n}'.length)
code = code.replace(/^ {2}/gm, '')
break
default:
exhaustiveSwitchError(kind)
}
return ['```ts', code, '```'].join('\n')
}
function addTags(result: Result, member: ApiItem) {
const tags = []
if (ApiReleaseTagMixin.isBaseClassOf(member)) {
tags.push(ReleaseTag[member.releaseTag])
}
if (ApiStaticMixin.isBaseClassOf(member) && member.isStatic) {
tags.push('Static')
}
if (ApiReadonlyMixin.isBaseClassOf(member) && member.isReadonly) {
tags.push('Readonly')
}
tags.push(member.kind)
result.markdown += `<Small>${tags.join(' ')}</Small>\n\n`
}
function addReferences(result: Result, member: ApiItem) {
if (!(member instanceof ApiDeclaredItem)) return
const references = new Set<string>()
member.excerptTokens.forEach((token) => {
if (token.kind !== 'Reference') return
const url = `/gen/${getPath(token as { canonicalReference: ApiItem['canonicalReference'] })}`
references.add(`[${token.text}](${url})`)
})
if (references.size) {
result.markdown += `##### References\n\n`
result.markdown += Array.from(references).join(', ') + '\n\n'
}
}

View file

@ -0,0 +1,11 @@
// import { buildDocs } from './build-docs'
import { generateContent } from './generateContent'
async function main() {
const { log } = console
log('Creating content for www.')
// await buildDocs()
await generateContent()
}
main()

120
apps/docs/scripts/utils.ts Normal file
View file

@ -0,0 +1,120 @@
import { ApiItem } from '@microsoft/api-extractor-model'
import {
DocCodeSpan,
DocEscapedText,
DocFencedCode,
DocNode,
DocParagraph,
DocPlainText,
DocSection,
DocSoftBreak,
} from '@microsoft/tsdoc'
import prettier from 'prettier'
export function getPath(item: { canonicalReference: ApiItem['canonicalReference'] }): string {
return item.canonicalReference
.toString()
.replace(/^@tldraw\/([^!]+)/, '$1/')
.replace(/[!:()#.]/g, '-')
.replace(/-+/g, '-')
.replace(/^-/, '')
.replace(/\/-/, '/')
.replace(/-$/, '')
}
export function getSlug(item: { canonicalReference: ApiItem['canonicalReference'] }): string {
return getPath(item).replace(/^[^/]+\//, '')
}
const prettierConfigPromise = prettier.resolveConfig(__dirname)
const languages: { [tag: string]: string | undefined } = {
ts: 'typescript',
tsx: 'typescript',
}
export async function formatWithPrettier(
code: string,
{
languageTag,
// roughly the width of our code blocks on a desktop
printWidth = 80,
}: { languageTag?: string; printWidth?: number } = {}
) {
const language = languages[languageTag || 'ts']
if (!language) {
throw new Error(`Unknown language: ${languageTag}`)
}
const prettierConfig = await prettierConfigPromise
const formattedCode = prettier.format(code, {
...prettierConfig,
parser: language,
printWidth,
tabWidth: 2,
useTabs: false,
})
return formattedCode.trimEnd()
}
export class MarkdownWriter {
static async docNodeToMarkdown(docNode: DocNode) {
const writer = new MarkdownWriter()
await writer.writeDocNode(docNode)
return writer.toString()
}
private result = ''
write(...parts: string[]): this {
this.result += parts.join('')
return this
}
endsWith(str: string) {
return this.result.endsWith(str)
}
writeIfNeeded(str: string): this {
if (!this.endsWith(str)) {
this.write(str)
}
return this
}
async writeDocNode(docNode: DocNode) {
if (docNode instanceof DocPlainText) {
this.write(docNode.text)
} else if (docNode instanceof DocSection || docNode instanceof DocParagraph) {
await this.writeDocNodes(docNode.nodes)
this.writeIfNeeded('\n\n')
} else if (docNode instanceof DocSoftBreak) {
this.writeIfNeeded('\n')
} else if (docNode instanceof DocCodeSpan) {
this.write('`', docNode.code, '`')
} else if (docNode instanceof DocFencedCode) {
this.writeIfNeeded('\n').write(
'```',
docNode.language,
'\n',
await formatWithPrettier(docNode.code, { languageTag: docNode.language }),
'\n',
'```\n'
)
} else if (docNode instanceof DocEscapedText) {
this.write(docNode.encodedText)
} else {
throw new Error(`Unknown docNode kind: ${docNode.kind}`)
}
}
async writeDocNodes(docNodes: readonly DocNode[]) {
for (const docNode of docNodes) {
await this.writeDocNode(docNode)
}
return this
}
toString() {
return this.result
}
}

View file

@ -0,0 +1,899 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&family=Roboto+Mono:wght@400;700&display=swap');
:root {
--font-body: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
--font-heading: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
--font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', 'Roboto Mono',
'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', 'Fira Mono', 'Droid Sans Mono',
'Courier New', monospace;
--color-tint-1: rgba(144, 144, 144, 0.1);
--color-tint-2: rgba(144, 144, 144, 0.15);
--color-tint-3: rgba(144, 144, 144, 0.3);
--color-tint-4: rgba(144, 144, 144, 0.5);
--color-tint-5: rgb(144, 144, 144);
--color-tint-6: rgb(81, 81, 81);
/* Light theme */
--color-text: #2d2d2d;
--color-background: #ffffff;
--color-accent: #2f80ed;
--shadow-small: 0px 0px 16px -2px rgba(0, 0, 0, 0.12), 0px 0px 4px 0px rgba(0, 0, 0, 0.12);
--shadow-big: 0px 0px 16px -2px rgba(0, 0, 0, 0.24), 0px 0px 4px 0px rgba(0, 0, 0, 0.24);
}
[data-theme='dark'] {
/* Dark theme */
--color-text: #fff;
--color-background: #1d1d1d;
--color-accent: #f3c14b;
--color-tint-6: rgb(186, 186, 186);
--shadow-small: 0px 0px 16px -2px rgba(0, 0, 0, 0.52), 0px 0px 4px 0px rgba(0, 0, 0, 0.62);
--shadow-big: 0px 0px 16px -2px rgba(0, 0, 0, 0.54), 0px 0px 4px 0px rgba(0, 0, 0, 0.54);
}
/* @media (prefers-color-scheme: dark) {
:root {
--color-text: #ffffff;
--color-background: #1d1d1d;
}
} */
/* @media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
} */
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html {
background-color: var(--color-background);
}
body {
font-family: var(--font-body);
font-size: 16px;
line-height: 1.5;
font-weight: 400;
color: var(--color-text);
background-color: var(--color-background);
transition: background-color 0.2s ease-in-out;
}
.layout {
display: grid;
grid-template-columns: 212px 1fr;
grid-template-rows: 1fr;
width: 100%;
max-width: 984px;
column-gap: 28px;
row-gap: 0px;
margin: 0px auto;
padding-top: 40px;
}
.icon-button {
display: block;
position: relative;
height: 40px;
width: 40px;
display: flex;
align-items: center;
justify-content: center;
background: none;
border: none;
--bg: transparent;
color: var(--color-text);
}
.icon-button::after {
position: absolute;
display: block;
content: '';
inset: 4px;
background-color: var(--bg);
border-radius: 4px;
transition: background-color 0.1s ease-in-out;
transition-delay: 0s;
}
@media (hover: hover) {
.icon-button:hover {
--bg: var(--color-tint-1);
color: var(--color-accent);
transition: background-color 0.2s ease-in-out;
transition-delay: 0.1s;
}
}
.icon {
flex-shrink: 0;
width: 16px;
height: 16px;
background-color: currentColor;
}
.lockup {
position: relative;
width: calc(71px * 4);
height: calc(18px * 4);
background: currentColor;
margin-bottom: 40px;
}
@media (max-width: 600px) {
.lockup {
width: calc(71px * 3);
height: calc(18px * 3);
}
.lockup + h2 {
font-size: 18px;
}
}
.article {
padding: 16px 16px 120px 16px;
font-weight: 400;
max-width: 100%;
overflow-x: hidden;
overflow-y: visible;
}
.article__details {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin: 40px 0px;
gap: 16px;
max-width: 100%;
overflow: hidden;
}
.article__details__edit {
display: flex;
align-items: center;
gap: 5px;
font-size: 14px;
flex-shrink: 0;
}
.article__details__timestamp {
font-size: 14px;
color: var(--color-tint-5);
text-align: right;
}
/* Prev / Next Links */
.article__links {
width: 100%;
display: grid;
grid-template-columns: 1fr 1fr;
align-items: flex-start;
justify-content: space-between;
max-width: 100%;
}
.article__links .icon {
display: inline-block;
}
.article__links__link {
display: block;
display: flex;
align-items: center;
gap: 8px;
border: 1px solid var(--color-accent);
padding: 10px 16px;
border-radius: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.article__links__prev {
grid-column: 1;
justify-self: flex-start;
}
.article__links__next {
grid-column: 2;
justify-self: flex-end;
}
.article__title {
display: grid;
grid-template-columns: 1fr auto;
grid-template-rows: auto;
}
/* Common Styles */
.article > * {
content-visibility: auto;
}
.article > h2,
h3,
h4,
h5,
h6 {
line-height: 1.2;
}
.article h1 {
font-size: 2.5em;
margin-bottom: 32px;
line-height: 1.15;
word-break: break-all;
}
.article h2 {
margin: 48px 0px 40px 0px;
font-size: 1.8em;
/* margin: 24px 0px 40px 0px; */
}
.article h5 {
text-transform: uppercase;
color: var(--color-tint-5);
letter-spacing: 0.1em;
font-size: 12px;
font-weight: 400;
}
.article details {
margin: 32px 0px;
}
.article details > summary {
display: flex;
align-items: center;
list-style: none;
}
.article details > summary::before {
font-size: 10px;
content: '▶';
margin-right: 8px;
}
.article details[open] > summary::before {
content: '▼';
}
.article p.article__small {
margin: 8px 0px 32px 0px;
}
.article p.article__small small {
font-size: 14px;
color: var(--color-tint-6);
}
.article a {
color: var(--color-accent);
text-decoration: none;
cursor: pointer;
}
@media (hover: hover) {
.article a:hover {
text-decoration: underline;
}
}
.article > p {
margin: 32px 0px;
}
.article > blockquote {
max-width: 100%;
margin: 32px 0px;
padding-left: 16px;
border-left: 2px solid var(--color-tint-2);
}
.article pre {
max-width: 100%;
margin: 32px 0px;
padding: 16px;
background-color: var(--color-tint-1);
border-radius: 8px;
font-size: 13px;
overflow-x: auto;
}
.article code {
font-family: var(--font-mono);
}
.article p > code,
.article td > code {
background-color: var(--color-tint-1);
font-size: 15px;
padding: 2px 4px;
margin: 0px -2px;
border-radius: 4px;
}
.article pre > code {
font-size: 14px;
width: 100%;
tab-size: 16px;
}
.article ol {
margin: 24px 0px;
padding-left: 16px;
}
.article ul {
margin: 24px 0px;
padding-left: 16px;
}
.article li {
margin: 8px 0px;
}
.article hr {
margin: 52px 0px;
padding: 0px 4px;
height: 1px;
width: calc(100% - 8px);
background-color: var(--color-tint-2);
border: none;
}
.article table {
margin: 32px 0px;
border-radius: 4px;
overflow: hidden;
width: 100%;
text-align: left;
border: 1px solid var(--color-tint-2);
}
.article table th {
padding: 8px 16px;
font-weight: 400;
font-size: 12px;
text-transform: uppercase;
color: var(--color-tint-5);
letter-spacing: 0.1em;
border-bottom: 1px solid var(--color-tint-2);
}
.article td:nth-of-type(1) {
width: 25%;
max-width: 50%;
}
.article table td {
padding: 8px 16px;
}
.article table td:nth-of-type(1) {
font-family: var(--font-mono);
}
.artcle__image {
display: block;
max-width: 100%;
margin: 24px 0px;
display: flex;
flex-direction: column;
gap: 12px;
}
.artcle__image > img {
width: 100%;
max-width: 100%;
height: auto;
border-radius: 4px;
}
.article__caption {
display: block;
text-align: center;
font-size: 14px;
width: 100%;
}
.breadcrumb {
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--color-tint-6);
margin-bottom: 8px;
}
.breadcrumb a {
color: inherit;
}
/* --------------------- Sidebar -------------------- */
.sidebar {
position: relative;
position: sticky;
padding: 8px;
align-self: start;
top: 0px;
z-index: 1000;
overflow-y: auto;
}
.sidebar .sidebar__list h4 {
padding: 12px;
}
.sidebar a {
color: inherit;
text-decoration: none;
cursor: pointer;
}
.sidebar hr {
margin: 11px 4px;
padding: 0px 4px;
height: 1px;
width: calc(100% - 8px);
background-color: var(--color-tint-2);
border: none;
}
.sidebar__list {
padding: 0px;
margin: 0px;
list-style-type: none;
}
.sidebar__link {
position: relative;
height: 40px;
padding: 0px 12px;
display: flex;
align-items: center;
justify-content: flex-start;
color: var(--color-text);
--bg: transparent;
margin-top: -4px;
margin-bottom: -4px;
}
.sidebar__link:nth-of-type(1) {
margin-top: 0px;
}
.sidebar__link:nth-last-of-type(1) {
margin-bottom: 0px;
}
.sidebar__link[data-active='true'] {
--bg: var(--color-tint-2);
}
.sidebar__link::after {
position: absolute;
display: block;
content: '';
inset: 4px;
background-color: var(--bg);
border-radius: 4px;
transition: background-color 0.2s ease-in-out;
transition-delay: 0s;
}
.sidebar__section__title {
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--color-tint-5);
}
.sidebar__sections__list > *:nth-of-type(1) {
padding-top: 12px;
}
.sidebar__sections__list > *:nth-last-of-type(n + 2) .sidebar__list {
padding-bottom: 12px;
margin-bottom: 12px;
border-bottom: 1px solid var(--color-tint-2);
}
@media (hover: hover) {
.sidebar__link:hover {
color: var(--color-accent);
transition: background-color 0.12s ease-in-out;
transition-delay: 0.1s;
}
.sidebar__link:not([data-active='true']):hover {
--bg: var(--color-tint-1);
}
}
.sidebar__footer {
padding: 12px;
padding-top: 44px;
font-size: 13px;
color: var(--color-tint-3);
display: flex;
flex-direction: column;
transition: color 0.2s ease-in-out;
transition-delay: 0s;
gap: 8px;
}
@media (hover: hover) {
.sidebar__footer:hover {
color: var(--color-text);
transition: color 0.3s ease-in-out;
transition-delay: 0.2s;
}
}
.sidebar__lockup {
position: relative;
width: calc(71px * 1.3);
height: calc(18px * 1.3);
background: currentColor;
}
.sidebar__buttons {
display: flex;
justify-content: space-between;
}
.sidebar__buttons__socials {
display: flex;
}
.sidebar__close {
display: none;
width: 100%;
align-items: center;
justify-content: flex-end;
color: var(--color-text);
position: fixed;
bottom: 0px;
left: 0px;
width: 100%;
padding: 8px;
font-size: 14px;
gap: 2px;
}
.menu__button {
display: none;
position: fixed;
z-index: 500;
bottom: 8px;
right: 8px;
background-color: var(--color-text);
border-radius: 6px;
color: var(--color-background);
box-shadow: var(--shadow-small);
}
/* --------------------- Search --------------------- */
.search__wrapper {
display: flex;
flex-direction: column;
}
.search {
position: relative;
z-index: 200;
height: 40px;
padding: 4px;
}
.search__icon {
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 12px;
z-index: 2;
pointer-events: none;
}
.search__input {
position: relative;
padding-left: 28px;
height: 100%;
width: 100%;
border-radius: 4px;
border: 1px solid var(--color-tint-3);
background-color: var(--color-background);
font-family: var(--font-body);
font-size: 14px;
background-color: none;
background: none;
}
.search__input::placeholder {
color: var(--color-tint-4);
}
.search__input:focus {
outline: none;
border-color: var(--color-text);
}
.search__results__wrapper {
position: relative;
height: 0px;
width: 100%;
z-index: 100;
}
.search__results {
padding: 0px;
position: absolute;
top: 0px;
left: 0px;
background-color: var(--color-background);
width: 320px;
border-radius: 8px;
box-shadow: var(--shadow-big);
max-height: 80vh;
overflow-y: auto;
}
.search__results__list {
list-style-type: none;
padding: 4px;
margin: 0px;
}
.search__results__article {
flex-direction: column;
align-items: flex-start;
justify-content: center;
font-size: 16px;
padding: 12px;
height: fit-content;
border-radius: 12px;
}
.search__results__article h4 {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 500;
color: var(--color-tint-5);
margin-bottom: 4px;
}
.search__results__article h3 {
font-size: 16px;
font-weight: 500;
}
@media (hover: hover) {
.sidebar__article:hover {
color: var(--color-accent);
transition: background-color 0.12s ease-in-out;
transition-delay: 0.1s;
}
.sidebar__article:not([data-active='true']):hover {
--bg: var(--color-tint-1);
}
}
/* --------------------- API docs --------------------- */
.parametersTable {
table-layout: fixed;
}
.parametersTable > thead th:nth-child(1) {
width: 25%;
}
.parametersTable > thead th:nth-child(2) {
width: 75%;
}
.parametersTable pre {
margin-top: 0;
margin-bottom: 16px;
}
.parametersTable-row > td {
vertical-align: top;
}
.parametersTable-row:not(:last-child) > td {
border-bottom: 1px solid var(--color-tint-1);
}
/* --------------------- Mobile --------------------- */
@media (max-width: 760px) {
.layout {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
width: 100%;
max-width: 984px;
column-gap: 28px;
row-gap: 0px;
margin: 0px auto;
padding-top: 16px;
}
.menu__button {
display: flex;
pointer-events: all;
}
.sidebar {
display: none;
background-color: var(--color-background);
}
.sidebar[data-open='true'] {
display: initial;
position: fixed;
inset: 0px;
z-index: 10000;
}
.sidebar[data-open='true'] > .sidebar__close {
display: flex;
}
.sidebar__article {
height: 44px;
font-size: 16px;
}
.icon-button {
height: 44px;
width: 44px;
}
.search {
height: 44px;
}
.search__input {
font-size: 16px;
}
.search__results__article {
height: auto;
}
.search__results {
position: static;
box-shadow: none;
padding: 0px;
padding-top: 8px;
width: 100%;
}
.search__results__list {
padding: 0px;
list-style-type: none;
}
.search__results__wrapper {
height: auto;
}
.article {
padding-top: 32px;
}
.article h1 {
font-size: 36px;
}
.article__links {
grid-template-columns: 1fr;
grid-auto-rows: 44px;
gap: 24px;
}
.article__links__prev {
border: none;
grid-row: 2;
grid-column: 2;
justify-self: flex-end;
}
.article__links__next {
grid-row: 1;
grid-column: 2;
justify-self: flex-end;
}
}
/* ------------------- Code Themes ------------------ */
.hljs {
color: var(--color-text);
}
.hljs-comment,
.hljs-quote {
color: #5c6370;
font-style: italic;
}
.hljs-doctag,
.hljs-keyword,
.hljs-formula {
color: #c678dd;
}
.hljs-section,
.hljs-name,
.hljs-selector-tag,
.hljs-deletion,
.hljs-subst {
color: #e06c75;
}
.hljs-literal {
color: #56b6c2;
}
.hljs-string,
.hljs-regexp,
.hljs-addition,
.hljs-attribute,
.hljs-meta .hljs-string {
color: #98c379;
}
.hljs-attr,
.hljs-variable,
.hljs-template-variable,
.hljs-type,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo,
.hljs-number {
color: #d19a66;
}
.hljs-symbol,
.hljs-bullet,
.hljs-link,
.hljs-meta,
.hljs-selector-id,
.hljs-title {
color: #61aeee;
}
.hljs-built_in,
.hljs-title.class_,
.hljs-class .hljs-title {
color: #e6c07b;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
.hljs-link {
text-decoration: underline;
}

View file

@ -0,0 +1,6 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs"
}
}

24
apps/docs/tsconfig.json Normal file
View file

@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "content.d.ts"],
"exclude": ["node_modules"],
"references": [{ "path": "../../packages/utils" }]
}

View file

@ -0,0 +1,114 @@
export enum Status {
Draft = 'draft',
Published = 'published',
}
/** A tablekeyed by slug of generated markdown content for each item */
export type MarkdownContent = Record<string, string>
/** A table keyed by slug of articles. */
export type Articles = Record<string, Article>
export interface Section {
/** The section's id */
id: string
/** The section's title */
title: string
/** A desscription of the section. */
description: string
/** A table keyed by category of each category. */
categories: Category[]
}
export type Category = {
/** The category's id */
id: string
/** The category's title */
title: string
/** A desscription of the category. */
description: string
/** An ordered array of articleIds that belong to this category. */
articleIds: string[]
groups: Group[]
}
export type Group = {
id: string
title: string
}
export interface Author {
name: string
image: string
email: string
twitter: string
}
export interface Article {
/** The unique id or "slug" for this article. */
id: string
/** The id of the group to which this article belongs. */
groupId: string
/** The index of this article inside of the article's group. */
groupIndex: number
/** The id of the category to which this article belongs. */
categoryId: string
/** The index of this article inside of the article's category. */
categoryIndex: number
/** The id of the section to which this article belongs. */
sectionId: string
/** The index of this article inside of the article's section. */
sectionIndex: number
/** The article's display title. */
title: string
/** The article's display description (optional). */
description: string | null
/** The article's author details (optional). */
author: Author | null
/** The article's hero image (optional). */
hero: string | null
/** The article's status (draft, published, hidden, etc) */
status: Status
/** The date on which the article was published (optional). */
date: string | null
/** An array of keywords associated with this article. */
keywords: string[]
/** The URL where the article's source can be found. */
sourceUrl: string
/** The articleId of the next article in the category. */
next: string | null
/** The articleId of the previous article in the category. */
prev: string | null
}
export type ArticleLinks = {
prev: Article | null
next: Article | null
}
export type SidebarContentSectionLink = {
type: 'section'
title: string
url: string
children: SidebarContentLink[]
}
export type SidebarContentCategoryLink = {
type: 'category'
title: string
url: string
children: SidebarContentLink[]
}
export type SidebarContentArticleLink = { type: 'article'; title: string; url: string }
export type SidebarContentLink =
| SidebarContentSectionLink
| SidebarContentCategoryLink
| SidebarContentArticleLink
export type SidebarContentList = {
sectionId: string | null
categoryId: string | null
articleId: string | null
links: SidebarContentLink[]
}
export type GeneratedContent = { sections: Section[]; content: MarkdownContent; articles: Articles }

View file

@ -0,0 +1,7 @@
export type SearchResult = {
type: 'article' | 'category' | 'section'
id: string
subtitle: string
title: string
url: string
}

124
apps/docs/utils/content.ts Normal file
View file

@ -0,0 +1,124 @@
import { serialize } from 'next-mdx-remote/serialize'
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
import rehypeHighlight from 'rehype-highlight'
import rehypeSlug from 'rehype-slug-custom-id'
import remarkGfm from 'remark-gfm'
import { scope } from '../components/mdx-components'
import { Article, GeneratedContent, Section, Status } from '../types/content-types'
import jsonContent from '../content.json' // this won't be here until we've run generate-content
export async function getMdxSource(source: string) {
return serialize(source, {
scope,
mdxOptions: {
remarkPlugins: [remarkGfm],
rehypePlugins: [
rehypeHighlight,
rehypeAutolinkHeadings,
[rehypeSlug, { enableCustomId: true, maintainCase: false, removeAccents: true }],
],
},
})
}
export async function getContent(): Promise<GeneratedContent> {
return jsonContent as any
}
export async function getMarkdownContent() {
return (await getContent()).content as Record<string, string>
}
export async function getArticles() {
return (await getContent()).articles as Record<string, Article>
}
export async function getSections() {
return (await getContent()).sections as Section[]
}
export async function getSection(sectionId: string) {
const sections = await getSections()
return sections.find((section) => section.id === sectionId)!
}
export async function getCategories(sectionId: string) {
return Object.values((await getSection(sectionId)).categories!)
}
export async function getCategory(sectionId: string, id: string) {
return (await getSection(sectionId)).categories!.find((c) => c.id === id)!
}
export async function getCategoryItems(sectionId: string, id: string) {
const section = await getSection(sectionId)
const category = section.categories!.find((c) => c.id === id)!
const articles = await getArticles()
return category.articleIds.map((id) => articles[id])
}
export async function getArticle(articleId: string) {
const article = (await getArticles())[articleId]
if (process.env.NODE_ENV !== 'development' && article.status !== Status.Published) {
throw Error(`Could not find a article with articleId ${articleId}`)
}
return article
}
export async function getArticleSource(articleId: string) {
const markdown = await getMarkdownContent()
return getMdxSource(markdown[articleId])
}
export async function getLinks(articleId: string) {
const article = (await getArticles())[articleId]
if (!article) throw Error(`Could not find a article with articleId ${articleId}`)
return {
prev: article.prev ? await getArticle(article.prev) : null,
next: article.next ? await getArticle(article.next) : null,
}
}
export async function getArticlePathsForSection(sectionId: string) {
const section = await getSection(sectionId)
return section.categories.map((category) => ({
params: { sectionId, categoryId: category.id },
}))
}
export async function getArticlePathsForCategory(sectionId: string, categoryId: string) {
const section = await getSection(sectionId)
const category = section.categories!.find((c) => c.id === categoryId)!
return category.articleIds.map((articleId) => ({
params: { sectionId, categoryId, articleId },
}))
}
export async function getCategoryPaths(sectionId: string) {
const section = await getSection(sectionId)
const results: { params: { sectionId: string; categoryId: string } }[] = []
if (section.categories) {
for (const category of section.categories) {
// for (const articleId of category.articleIds) {
results.push({ params: { sectionId, categoryId: category.id } })
// }
}
}
return results
}
export async function getAllSlugsForSection(sectionId: string) {
const section = await getSection(sectionId)
const results: { params: { sectionId: string; categoryId: string; articleId: string } }[] = []
if (section.categories) {
for (const category of section.categories) {
for (const articleId of category.articleIds) {
results.push({ params: { sectionId, categoryId: category.id, articleId } })
}
}
}
return results
}

View file

@ -0,0 +1,83 @@
import { SidebarContentLink, SidebarContentList } from '../types/content-types'
import { getArticles, getSections } from './content'
export async function getSidebarContentList({
sectionId,
categoryId,
articleId,
}: {
sectionId?: string
categoryId?: string
articleId?: string
}): Promise<SidebarContentList> {
const links: SidebarContentLink[] = []
const articles = await getArticles()
for (const section of await getSections()) {
const children: SidebarContentLink[] = []
if (section.id === 'gen') {
links.push({ type: 'article', title: 'API Reference', url: '/gen' })
// for (const category of section.categories) {
// if (category.id === 'ucg') {
// continue
// } else {
// children.push({
// type: 'article',
// title: category.title,
// url: `/${section.id}/${category.id}`,
// })
// }
// }
// links.push({ type: 'section', title: 'API Reference', url: '/gen', children })
continue
}
for (const category of section.categories) {
if (category.id === 'ucg') {
continue
} else {
children.push({
type: 'category',
title: category.title,
url: `/${section.id}/${category.id}`,
children: category.articleIds.map((articleId) => {
const article = articles[articleId]
return {
type: 'article' as const,
title: article.title,
url: `/${section.id}/${category.id}/${articleId}`,
}
}),
})
}
}
for (const category of section.categories) {
if (category.id === 'ucg') {
children.push(
...category.articleIds.map((articleId) => {
const article = articles[articleId]
return {
type: 'article' as const,
title: article.title,
url: `/${section.id}/${category.id}/${articleId}`,
}
})
)
}
}
links.push({ type: 'section', title: section.title, url: `/${section.id}`, children })
}
return {
sectionId: sectionId ?? null,
categoryId: categoryId ?? null,
articleId: articleId ?? null,
links,
}
}

Some files were not shown because too many files have changed in this diff Show more