├── .dockerignore
├── .env.example
├── .eslintrc.json
├── .github
├── assets
│ ├── cover.png
│ ├── dark
│ │ └── logo.svg
│ └── light
│ │ └── logo.svg
└── workflows
│ ├── main.yml
│ ├── publish-docs.yml
│ └── publish-landing-page.yml
├── .gitignore
├── .prettierrc.js
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── SECURITY.md
├── apps
├── backend
│ ├── api
│ │ ├── .env.example
│ │ ├── Dockerfile
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── api.ts
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── app
│ │ ├── .env.example
│ │ ├── Dockerfile
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── app.ts
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── assets
│ │ ├── .env.example
│ │ ├── Dockerfile
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── assets.ts
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── collaboration
│ │ ├── .env.example
│ │ ├── Dockerfile
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── extensions
│ │ │ │ ├── git-sync.ts
│ │ │ │ ├── search-indexing.ts
│ │ │ │ └── version-history.ts
│ │ │ ├── index.ts
│ │ │ └── writing.ts
│ │ └── tsconfig.json
│ ├── extensions
│ │ ├── .env.example
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── extensions.ts
│ │ │ ├── index.ts
│ │ │ └── routes
│ │ │ │ ├── dev
│ │ │ │ ├── index.ts
│ │ │ │ └── transformer.ts
│ │ │ │ ├── gpt.ts
│ │ │ │ ├── hashnode
│ │ │ │ ├── index.ts
│ │ │ │ └── transformer.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── mdx
│ │ │ │ ├── index.ts
│ │ │ │ ├── input-transformer.ts
│ │ │ │ └── output-transformer.ts
│ │ │ │ └── medium
│ │ │ │ ├── index.ts
│ │ │ │ └── transformer.ts
│ │ └── tsconfig.json
│ └── usage-reporting
│ │ ├── Dockerfile
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── src
│ │ └── index.ts
│ │ └── tsconfig.json
├── docs
│ ├── .astro
│ │ ├── icon.d.ts
│ │ └── types.d.ts
│ ├── .gitignore
│ ├── .vscode
│ │ ├── extensions.json
│ │ └── launch.json
│ ├── LICENSE.md
│ ├── README.md
│ ├── astro.config.mjs
│ ├── package.json
│ ├── public
│ │ ├── _redirects
│ │ ├── favicon.svg
│ │ ├── meta-image.png
│ │ ├── nunito-latin-ext-variable-wghtOnly-normal.woff2
│ │ └── nunito-latin-variable-wghtOnly-normal.woff2
│ ├── src
│ │ ├── assets
│ │ │ ├── icons
│ │ │ │ ├── discord.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── logo.ts
│ │ │ └── json
│ │ │ │ ├── dark-theme.json
│ │ │ │ └── light-theme.json
│ │ ├── components
│ │ │ ├── content
│ │ │ │ ├── card-grid.astro
│ │ │ │ ├── card.astro
│ │ │ │ ├── endpoint-card
│ │ │ │ │ ├── endpoint-card-sections.tsx
│ │ │ │ │ ├── endpoint-card.astro
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── openapi-spec.ts
│ │ │ │ │ ├── param-description.tsx
│ │ │ │ │ ├── rehype-examples.ts
│ │ │ │ │ ├── request-example.astro
│ │ │ │ │ ├── request-examples.tsx
│ │ │ │ │ ├── response-example.astro
│ │ │ │ │ └── utils.ts
│ │ │ │ ├── important.astro
│ │ │ │ ├── index.ts
│ │ │ │ └── info.astro
│ │ │ ├── fragments
│ │ │ │ ├── analytics.tsx
│ │ │ │ ├── base-head.astro
│ │ │ │ ├── footer.tsx
│ │ │ │ ├── header.tsx
│ │ │ │ ├── index.ts
│ │ │ │ ├── on-this-page.tsx
│ │ │ │ ├── scrollbar-width.tsx
│ │ │ │ ├── search-palette.tsx
│ │ │ │ ├── side-bar.tsx
│ │ │ │ └── svg-defs.tsx
│ │ │ ├── layouts
│ │ │ │ ├── default.astro
│ │ │ │ ├── index.ts
│ │ │ │ └── menu.json
│ │ │ └── primitives
│ │ │ │ ├── image.tsx
│ │ │ │ └── index.tsx
│ │ ├── content
│ │ │ ├── api
│ │ │ │ ├── authentication.md
│ │ │ │ ├── content-groups.mdx
│ │ │ │ ├── content-pieces.mdx
│ │ │ │ ├── extension.mdx
│ │ │ │ ├── profile.mdx
│ │ │ │ ├── roles.mdx
│ │ │ │ ├── search.mdx
│ │ │ │ ├── tags.mdx
│ │ │ │ ├── transformers.mdx
│ │ │ │ ├── user-settings.mdx
│ │ │ │ ├── variants.mdx
│ │ │ │ ├── webhooks.mdx
│ │ │ │ ├── workspace-memberships.mdx
│ │ │ │ ├── workspace-settings.mdx
│ │ │ │ └── workspace.mdx
│ │ │ ├── config.ts
│ │ │ └── docs
│ │ │ │ ├── getting-started
│ │ │ │ ├── concepts.mdx
│ │ │ │ └── introduction.mdx
│ │ │ │ ├── javascript-sdk
│ │ │ │ └── javascript-sdk.md
│ │ │ │ ├── self-hosting
│ │ │ │ ├── configuration.mdx
│ │ │ │ └── docker.mdx
│ │ │ │ └── usage-guide
│ │ │ │ ├── configuring-vrite.md
│ │ │ │ ├── content-editor.md
│ │ │ │ ├── extensions
│ │ │ │ ├── introduction.mdx
│ │ │ │ └── official-extensions
│ │ │ │ │ ├── dev.mdx
│ │ │ │ │ ├── gpt.mdx
│ │ │ │ │ ├── hashnode.mdx
│ │ │ │ │ ├── mdx.mdx
│ │ │ │ │ └── medium.mdx
│ │ │ │ ├── metadata.md
│ │ │ │ └── navigation
│ │ │ │ ├── command-palette.md
│ │ │ │ ├── dashboard.md
│ │ │ │ ├── explorer.md
│ │ │ │ └── navigation.mdx
│ │ ├── env.d.ts
│ │ ├── lib
│ │ │ └── state.ts
│ │ ├── pages
│ │ │ └── [...slug].astro
│ │ └── styles
│ │ │ ├── base.css
│ │ │ └── styles.scss
│ ├── tsconfig.json
│ ├── unocss-preset-forms.config.js
│ └── unocss.config.js
├── landing-page
│ ├── .env.example
│ ├── .gitignore
│ ├── .npmrc
│ ├── .vscode
│ │ ├── extensions.json
│ │ └── launch.json
│ ├── LICENSE.md
│ ├── README.md
│ ├── astro.config.mjs
│ ├── package.json
│ ├── public
│ │ ├── favicon.svg
│ │ └── meta-image.png
│ ├── src
│ │ ├── assets
│ │ │ └── images
│ │ │ │ ├── dark
│ │ │ │ ├── customize.png
│ │ │ │ ├── editor.png
│ │ │ │ ├── explorer.png
│ │ │ │ ├── extensions.png
│ │ │ │ ├── hero.png
│ │ │ │ ├── hybrid-editor.png
│ │ │ │ ├── kanban-view.png
│ │ │ │ ├── roles.png
│ │ │ │ ├── search-small.png
│ │ │ │ ├── search.png
│ │ │ │ └── table-view.png
│ │ │ │ └── light
│ │ │ │ ├── customize.png
│ │ │ │ ├── editor.png
│ │ │ │ ├── explorer.png
│ │ │ │ ├── extensions.png
│ │ │ │ ├── hero.png
│ │ │ │ ├── hybrid-editor.png
│ │ │ │ ├── roles.png
│ │ │ │ ├── search-small.png
│ │ │ │ ├── search.png
│ │ │ │ └── table-view.png
│ │ ├── components
│ │ │ ├── fragments
│ │ │ │ ├── analytics.tsx
│ │ │ │ ├── base-head.astro
│ │ │ │ ├── footer.astro
│ │ │ │ ├── header.tsx
│ │ │ │ ├── index.ts
│ │ │ │ ├── observed.tsx
│ │ │ │ ├── parallax-card.tsx
│ │ │ │ ├── section-headline.astro
│ │ │ │ ├── section.astro
│ │ │ │ └── svg-defs.astro
│ │ │ ├── primitives
│ │ │ │ ├── image.tsx
│ │ │ │ └── index.ts
│ │ │ └── sections
│ │ │ │ ├── customize
│ │ │ │ ├── features-grid.tsx
│ │ │ │ └── index.astro
│ │ │ │ ├── editor
│ │ │ │ └── index.astro
│ │ │ │ ├── get-started
│ │ │ │ ├── cta-grid.tsx
│ │ │ │ └── index.astro
│ │ │ │ ├── headless
│ │ │ │ ├── features-carousel.tsx
│ │ │ │ ├── index.astro
│ │ │ │ └── secondary-feature-card.tsx
│ │ │ │ ├── hero
│ │ │ │ ├── index.astro
│ │ │ │ └── scroll-indicator.tsx
│ │ │ │ ├── index.ts
│ │ │ │ ├── manage
│ │ │ │ └── index.astro
│ │ │ │ ├── pricing
│ │ │ │ ├── index.astro
│ │ │ │ ├── pricing-card.tsx
│ │ │ │ └── pricing-plans.tsx
│ │ │ │ └── search
│ │ │ │ └── index.astro
│ │ ├── env.d.ts
│ │ ├── icons
│ │ │ ├── javascript.ts
│ │ │ ├── linkedin.ts
│ │ │ ├── logo.ts
│ │ │ ├── open-source.ts
│ │ │ └── sdk.ts
│ │ ├── lib
│ │ │ ├── email.ts
│ │ │ ├── ref.ts
│ │ │ └── utils.ts
│ │ ├── pages
│ │ │ ├── blog
│ │ │ │ ├── [slug].astro
│ │ │ │ └── index.astro
│ │ │ ├── index.astro
│ │ │ ├── pricing.astro
│ │ │ ├── privacy.astro
│ │ │ └── tos.astro
│ │ └── styles
│ │ │ ├── base.css
│ │ │ └── blog.scss
│ ├── tsconfig.json
│ └── unocss.config.js
└── web
│ ├── index.html
│ ├── package.json
│ ├── public
│ ├── favicon.svg
│ ├── icons
│ │ ├── 512.png
│ │ └── favicon.png
│ ├── jetbrains-mono-wghtOnly-normal.woff2
│ ├── manifest.json
│ ├── nunito-latin-ext-variable-wghtOnly-normal.woff2
│ ├── nunito-latin-variable-wghtOnly-normal.woff2
│ └── sandbox.js
│ ├── scripts
│ └── sandbox.ts
│ ├── src
│ ├── app.tsx
│ ├── assets
│ │ ├── icons
│ │ │ ├── api.ts
│ │ │ ├── codesandbox.ts
│ │ │ ├── discord.ts
│ │ │ ├── drag-vertical.ts
│ │ │ ├── google.ts
│ │ │ ├── hashnode.ts
│ │ │ ├── index.ts
│ │ │ ├── logo.ts
│ │ │ ├── mdx.ts
│ │ │ └── sidebar-collapse.ts
│ │ └── json
│ │ │ ├── dark-theme.json
│ │ │ └── light-theme.json
│ ├── components
│ │ ├── fragments
│ │ │ ├── collapsible-section.tsx
│ │ │ ├── index.tsx
│ │ │ ├── input-field.tsx
│ │ │ ├── mini-code-editor.tsx
│ │ │ ├── mini-editor.tsx
│ │ │ ├── scroll-shadow.tsx
│ │ │ ├── searchable-select.tsx
│ │ │ └── svg-defs.tsx
│ │ └── primitives
│ │ │ ├── draggable.tsx
│ │ │ ├── droppable.tsx
│ │ │ ├── index.ts
│ │ │ └── sortable.tsx
│ ├── context
│ │ ├── appearance.tsx
│ │ ├── authenticated-user-data.tsx
│ │ ├── client.tsx
│ │ ├── command-palette
│ │ │ └── index.tsx
│ │ ├── comments.tsx
│ │ ├── confirmation-modal.tsx
│ │ ├── content
│ │ │ ├── actions.ts
│ │ │ ├── index.tsx
│ │ │ └── loader.ts
│ │ ├── extensions.tsx
│ │ ├── history.tsx
│ │ ├── host-config.tsx
│ │ ├── index.tsx
│ │ ├── local-storage.tsx
│ │ ├── meta.tsx
│ │ ├── notifications.tsx
│ │ ├── shared-state.tsx
│ │ └── snippets.tsx
│ ├── ee
│ │ ├── LICENSE.md
│ │ ├── index.ts
│ │ ├── settings
│ │ │ ├── billing
│ │ │ │ ├── api-usage-card.tsx
│ │ │ │ ├── change-plan-card.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── price-tag.tsx
│ │ │ └── index.ts
│ │ └── subscription-banner.tsx
│ ├── index.tsx
│ ├── layout
│ │ ├── bottom-menu.tsx
│ │ ├── index.ts
│ │ ├── secured-layout.tsx
│ │ ├── side-panel-right.tsx
│ │ ├── side-panel.tsx
│ │ ├── sidebar-menu.tsx
│ │ ├── toolbar
│ │ │ ├── breadcrumb.tsx
│ │ │ ├── export-menu.tsx
│ │ │ ├── index.tsx
│ │ │ ├── restore-version.tsx
│ │ │ ├── right-panel-menu.tsx
│ │ │ └── user-list.tsx
│ │ └── walkthrough.tsx
│ ├── lib
│ │ ├── code-editor
│ │ │ ├── format.ts
│ │ │ ├── index.ts
│ │ │ └── suggest-language.ts
│ │ ├── editor
│ │ │ ├── editing.ts
│ │ │ ├── extensions
│ │ │ │ ├── auto-dir.ts
│ │ │ │ ├── autocomplete
│ │ │ │ │ ├── decoration.ts
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── ui.tsx
│ │ │ │ ├── block-action-menu
│ │ │ │ │ ├── component.tsx
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── options.tsx
│ │ │ │ │ └── plugin.tsx
│ │ │ │ ├── block-paste.ts
│ │ │ │ ├── code-block
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── menu.tsx
│ │ │ │ │ ├── node.tsx
│ │ │ │ │ └── view.tsx
│ │ │ │ ├── collab-cursor.tsx
│ │ │ │ ├── collaboration
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── sync-plugin.js
│ │ │ │ ├── comment-menu
│ │ │ │ │ ├── comment-card.tsx
│ │ │ │ │ ├── comment-input.tsx
│ │ │ │ │ ├── comment-thread.tsx
│ │ │ │ │ ├── component.tsx
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── plugin.tsx
│ │ │ │ ├── diff.ts
│ │ │ │ ├── draggable-text.ts
│ │ │ │ ├── element
│ │ │ │ │ ├── custom-node-view.tsx
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── node.tsx
│ │ │ │ │ ├── selection.ts
│ │ │ │ │ ├── utils.ts
│ │ │ │ │ ├── view-manager.ts
│ │ │ │ │ └── xml-node-view.tsx
│ │ │ │ ├── embed
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── menu.tsx
│ │ │ │ │ ├── node.ts
│ │ │ │ │ └── view.tsx
│ │ │ │ ├── image
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── menu.tsx
│ │ │ │ │ ├── node.ts
│ │ │ │ │ └── view.tsx
│ │ │ │ ├── index.ts
│ │ │ │ ├── link-preview
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── plugin.ts
│ │ │ │ │ └── wrapper.tsx
│ │ │ │ ├── placeholder.ts
│ │ │ │ ├── shortcuts.ts
│ │ │ │ ├── slash-menu
│ │ │ │ │ ├── component.tsx
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── plugin.ts
│ │ │ │ ├── table-menu
│ │ │ │ │ ├── component.tsx
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── plugin.tsx
│ │ │ │ ├── trailing-node.ts
│ │ │ │ └── xml-node-menu
│ │ │ │ │ ├── component.tsx
│ │ │ │ │ ├── editor.tsx
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── plugin.tsx
│ │ │ └── index.ts
│ │ ├── extensions
│ │ │ ├── component-renderer.tsx
│ │ │ ├── extension-view-renderer.tsx
│ │ │ ├── index.ts
│ │ │ └── sandbox.ts
│ │ ├── monaco.ts
│ │ └── utils
│ │ │ ├── breakpoints.ts
│ │ │ ├── embeds.ts
│ │ │ ├── general.ts
│ │ │ ├── index.ts
│ │ │ ├── ref.ts
│ │ │ ├── selection.tsx
│ │ │ ├── tags.tsx
│ │ │ ├── upload-file.ts
│ │ │ └── validate.ts
│ ├── styles
│ │ ├── index.ts
│ │ ├── styles.css
│ │ └── styles.scss
│ ├── typings.d.ts
│ └── views
│ │ ├── auth
│ │ ├── error-messages.tsx
│ │ ├── index.tsx
│ │ ├── magic-link.tsx
│ │ ├── password.tsx
│ │ └── view.tsx
│ │ ├── comments
│ │ └── index.tsx
│ │ ├── conflict
│ │ └── index.tsx
│ │ ├── content-piece
│ │ ├── description.tsx
│ │ ├── index.tsx
│ │ ├── sections
│ │ │ ├── custom-data.tsx
│ │ │ ├── details
│ │ │ │ ├── canonical-link.tsx
│ │ │ │ ├── date.tsx
│ │ │ │ ├── filename.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ ├── members-dropdown.tsx
│ │ │ │ ├── members.tsx
│ │ │ │ ├── slug.tsx
│ │ │ │ ├── tags-dropdown.tsx
│ │ │ │ └── tags.tsx
│ │ │ ├── extensions.tsx
│ │ │ └── index.ts
│ │ └── title.tsx
│ │ ├── dashboard
│ │ ├── dashboard-context.tsx
│ │ ├── index.tsx
│ │ ├── kanban
│ │ │ ├── content-group-column.tsx
│ │ │ ├── content-piece-card.tsx
│ │ │ └── index.tsx
│ │ └── table
│ │ │ ├── content-piece-group.tsx
│ │ │ ├── content-piece-row.tsx
│ │ │ ├── index.tsx
│ │ │ ├── table-header.tsx
│ │ │ └── table-view-context.tsx
│ │ ├── editor
│ │ ├── content-piece-editor.tsx
│ │ ├── diff-editor
│ │ │ ├── change-indicators.tsx
│ │ │ └── index.tsx
│ │ ├── editor.tsx
│ │ ├── index.tsx
│ │ ├── menus
│ │ │ ├── bubble
│ │ │ │ ├── block.tsx
│ │ │ │ ├── format.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ ├── link.tsx
│ │ │ │ ├── select.tsx
│ │ │ │ └── table.tsx
│ │ │ ├── floating
│ │ │ │ └── index.tsx
│ │ │ ├── index.ts
│ │ │ └── link-preview
│ │ │ │ └── index.tsx
│ │ ├── snippet-editor.tsx
│ │ └── version-editor.tsx
│ │ ├── explorer
│ │ ├── content-group-row.tsx
│ │ ├── content-piece-row.tsx
│ │ ├── explorer-context.tsx
│ │ ├── index.tsx
│ │ ├── new-group-button.tsx
│ │ └── tree-level.tsx
│ │ ├── extensions
│ │ ├── extension-card.tsx
│ │ ├── extension-configuration-view.tsx
│ │ ├── extension-icon.tsx
│ │ ├── extensions-menu-view.tsx
│ │ ├── extensions-url-install-view.tsx
│ │ └── index.tsx
│ │ ├── getting-started
│ │ └── index.tsx
│ │ ├── git
│ │ ├── index.tsx
│ │ ├── initial-setup-view.tsx
│ │ ├── provider-configuration-view
│ │ │ ├── github.tsx
│ │ │ └── index.tsx
│ │ ├── providers.ts
│ │ └── sync-view
│ │ │ └── index.tsx
│ │ ├── history
│ │ ├── history-context.tsx
│ │ ├── history-entry.tsx
│ │ └── index.tsx
│ │ ├── settings
│ │ ├── api
│ │ │ ├── configure-action.tsx
│ │ │ ├── configure-subsection.tsx
│ │ │ └── index.tsx
│ │ ├── appearance.tsx
│ │ ├── editor
│ │ │ ├── index.tsx
│ │ │ └── options.ts
│ │ ├── image-upload.tsx
│ │ ├── index.tsx
│ │ ├── menu.tsx
│ │ ├── metadata.tsx
│ │ ├── profile.tsx
│ │ ├── security.tsx
│ │ ├── transformers
│ │ │ ├── configure-subsection.tsx
│ │ │ └── index.tsx
│ │ ├── variants
│ │ │ ├── configure-subsection.tsx
│ │ │ └── index.tsx
│ │ ├── view.tsx
│ │ ├── webhooks
│ │ │ ├── configure-webhook-subsection.tsx
│ │ │ ├── events.ts
│ │ │ └── index.tsx
│ │ └── workspace
│ │ │ ├── configure-role-subsection.tsx
│ │ │ ├── index.tsx
│ │ │ ├── information-card.tsx
│ │ │ ├── invite-member-subsection.tsx
│ │ │ ├── members-card.tsx
│ │ │ └── roles-card.tsx
│ │ ├── snippets
│ │ ├── index.tsx
│ │ ├── new-snippet-button.tsx
│ │ ├── snippet-entry.tsx
│ │ └── snippets-context.tsx
│ │ ├── verify
│ │ └── index.tsx
│ │ └── workspaces
│ │ ├── create-workspace-subsection.tsx
│ │ ├── index.tsx
│ │ ├── overlay.tsx
│ │ └── view.tsx
│ ├── tsconfig.json
│ ├── unocss-preset-forms.config.js
│ ├── unocss.config.js
│ └── vite.config.mts
├── docker-compose.yml
├── package.json
├── packages
├── backend
│ ├── package.json
│ ├── src
│ │ ├── assets
│ │ │ └── initial-content.json
│ │ ├── collections
│ │ │ ├── comment-threads.ts
│ │ │ ├── comments.ts
│ │ │ ├── content-groups.ts
│ │ │ ├── content-piece-variants.ts
│ │ │ ├── content-pieces.ts
│ │ │ ├── content-variants.ts
│ │ │ ├── content-versions.ts
│ │ │ ├── contents.ts
│ │ │ ├── extensions.ts
│ │ │ ├── git-data.ts
│ │ │ ├── index.ts
│ │ │ ├── roles.ts
│ │ │ ├── snippet-contents.ts
│ │ │ ├── snippets.ts
│ │ │ ├── tags.ts
│ │ │ ├── tokens.ts
│ │ │ ├── transformers.ts
│ │ │ ├── user-settings.ts
│ │ │ ├── users.ts
│ │ │ ├── variants.ts
│ │ │ ├── versions.ts
│ │ │ ├── webhooks.ts
│ │ │ ├── workspace-memberships.ts
│ │ │ ├── workspace-settings.ts
│ │ │ └── workspaces.ts
│ │ ├── ee
│ │ │ ├── LICENSE.md
│ │ │ ├── billing
│ │ │ │ ├── index.ts
│ │ │ │ ├── plugin.ts
│ │ │ │ └── route.ts
│ │ │ └── index.ts
│ │ ├── env.ts
│ │ ├── events
│ │ │ ├── comment.ts
│ │ │ ├── content-group.ts
│ │ │ ├── content-piece.ts
│ │ │ ├── extension.ts
│ │ │ ├── git-data.ts
│ │ │ ├── index.ts
│ │ │ ├── role.ts
│ │ │ ├── snippet.ts
│ │ │ ├── tag.ts
│ │ │ ├── token.ts
│ │ │ ├── transformer.ts
│ │ │ ├── user-settings.ts
│ │ │ ├── user.ts
│ │ │ ├── variant.ts
│ │ │ ├── versions.ts
│ │ │ ├── webhook.ts
│ │ │ ├── workspace-membership.ts
│ │ │ ├── workspace-settings.ts
│ │ │ └── workspace.ts
│ │ ├── index.ts
│ │ ├── lib
│ │ │ ├── auth.ts
│ │ │ ├── content-processing
│ │ │ │ ├── conversions.ts
│ │ │ │ ├── diff.ts
│ │ │ │ └── index.ts
│ │ │ ├── context.ts
│ │ │ ├── errors.ts
│ │ │ ├── git-sync
│ │ │ │ ├── index.ts
│ │ │ │ ├── process-content.ts
│ │ │ │ ├── provider.ts
│ │ │ │ └── providers
│ │ │ │ │ ├── github
│ │ │ │ │ ├── commit.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── initial-sync.ts
│ │ │ │ │ ├── pull.ts
│ │ │ │ │ └── requests.ts
│ │ │ │ │ └── index.ts
│ │ │ ├── hash.ts
│ │ │ ├── host-config.ts
│ │ │ ├── middleware.ts
│ │ │ ├── mongo.ts
│ │ │ ├── plugin.ts
│ │ │ ├── pub-sub.ts
│ │ │ ├── session.ts
│ │ │ ├── trpc.ts
│ │ │ ├── utils.ts
│ │ │ └── workspace.ts
│ │ ├── plugins
│ │ │ ├── database.ts
│ │ │ ├── email.ts
│ │ │ ├── git-sync
│ │ │ │ ├── handlers
│ │ │ │ │ ├── content-group-created.ts
│ │ │ │ │ ├── content-group-moved.ts
│ │ │ │ │ ├── content-group-removed.ts
│ │ │ │ │ ├── content-group-updated.ts
│ │ │ │ │ ├── content-piece-created.ts
│ │ │ │ │ ├── content-piece-moved.ts
│ │ │ │ │ ├── content-piece-removed.ts
│ │ │ │ │ └── content-piece-updated.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── utils.ts
│ │ │ ├── host-config.ts
│ │ │ ├── index.ts
│ │ │ ├── oauth.ts
│ │ │ ├── pub-sub.ts
│ │ │ ├── route-callbacks.ts
│ │ │ ├── s3.ts
│ │ │ ├── search
│ │ │ │ ├── content
│ │ │ │ │ ├── bulk-upsert.ts
│ │ │ │ │ ├── delete.ts
│ │ │ │ │ └── upsert.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── utils.ts
│ │ │ ├── session.ts
│ │ │ ├── trpc.ts
│ │ │ └── webhooks.ts
│ │ ├── routes
│ │ │ ├── auth
│ │ │ │ ├── handlers
│ │ │ │ │ ├── change-password.ts
│ │ │ │ │ ├── init-two-factor.ts
│ │ │ │ │ ├── is-signed-in.ts
│ │ │ │ │ ├── login.ts
│ │ │ │ │ ├── logout.ts
│ │ │ │ │ ├── refresh-token.ts
│ │ │ │ │ ├── register.ts
│ │ │ │ │ ├── send-magic-link.ts
│ │ │ │ │ └── switch-workspace.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── utils.ts
│ │ │ ├── comments
│ │ │ │ ├── handlers
│ │ │ │ │ ├── create-comment.ts
│ │ │ │ │ ├── create-thread.ts
│ │ │ │ │ ├── delete-comment.ts
│ │ │ │ │ ├── delete-thread.ts
│ │ │ │ │ ├── get-thread.ts
│ │ │ │ │ ├── list-comments.ts
│ │ │ │ │ ├── list-threads.ts
│ │ │ │ │ ├── resolve-thread.ts
│ │ │ │ │ └── update-comment.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── utils.ts
│ │ │ ├── content-groups
│ │ │ │ ├── handlers
│ │ │ │ │ ├── create.ts
│ │ │ │ │ ├── delete.ts
│ │ │ │ │ ├── get.ts
│ │ │ │ │ ├── list-ancestors.ts
│ │ │ │ │ ├── list.ts
│ │ │ │ │ ├── move.ts
│ │ │ │ │ ├── reorder.ts
│ │ │ │ │ └── update.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── utils.ts
│ │ │ ├── content-pieces
│ │ │ │ ├── handlers
│ │ │ │ │ ├── create.ts
│ │ │ │ │ ├── delete.ts
│ │ │ │ │ ├── get.ts
│ │ │ │ │ ├── list.ts
│ │ │ │ │ ├── move.ts
│ │ │ │ │ └── update.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── utils.ts
│ │ │ ├── extensions
│ │ │ │ ├── handlers
│ │ │ │ │ ├── configure.ts
│ │ │ │ │ ├── get.ts
│ │ │ │ │ ├── install.ts
│ │ │ │ │ ├── list.ts
│ │ │ │ │ ├── uninstall.ts
│ │ │ │ │ └── update-content-piece-data.ts
│ │ │ │ └── index.ts
│ │ │ ├── git
│ │ │ │ ├── handlers
│ │ │ │ │ ├── commit.ts
│ │ │ │ │ ├── configure.ts
│ │ │ │ │ ├── get-config.ts
│ │ │ │ │ ├── initial-sync.ts
│ │ │ │ │ ├── pull.ts
│ │ │ │ │ ├── reset-config.ts
│ │ │ │ │ └── resolve-conflict.ts
│ │ │ │ └── index.ts
│ │ │ ├── index.ts
│ │ │ ├── roles
│ │ │ │ ├── handlers
│ │ │ │ │ ├── create.ts
│ │ │ │ │ ├── delete.ts
│ │ │ │ │ ├── get.ts
│ │ │ │ │ ├── list.ts
│ │ │ │ │ └── update.ts
│ │ │ │ └── index.ts
│ │ │ ├── search
│ │ │ │ ├── handlers
│ │ │ │ │ ├── ask.ts
│ │ │ │ │ └── search.ts
│ │ │ │ └── index.ts
│ │ │ ├── snippets
│ │ │ │ ├── handlers
│ │ │ │ │ ├── create.ts
│ │ │ │ │ ├── delete.ts
│ │ │ │ │ ├── get.ts
│ │ │ │ │ ├── list.ts
│ │ │ │ │ └── update.ts
│ │ │ │ └── index.ts
│ │ │ ├── tags
│ │ │ │ ├── handlers
│ │ │ │ │ ├── create.ts
│ │ │ │ │ ├── delete.ts
│ │ │ │ │ ├── get.ts
│ │ │ │ │ ├── list.ts
│ │ │ │ │ ├── search.ts
│ │ │ │ │ └── update.ts
│ │ │ │ └── index.ts
│ │ │ ├── tokens
│ │ │ │ ├── handlers
│ │ │ │ │ ├── create.ts
│ │ │ │ │ ├── delete.ts
│ │ │ │ │ ├── get.ts
│ │ │ │ │ ├── list.ts
│ │ │ │ │ ├── regenerate.ts
│ │ │ │ │ └── update.ts
│ │ │ │ └── index.ts
│ │ │ ├── transformers
│ │ │ │ ├── handlers
│ │ │ │ │ ├── create.ts
│ │ │ │ │ ├── delete.ts
│ │ │ │ │ └── list.ts
│ │ │ │ └── index.ts
│ │ │ ├── user-settings
│ │ │ │ ├── handlers
│ │ │ │ │ ├── get-workspace-id.ts
│ │ │ │ │ ├── get.ts
│ │ │ │ │ └── update.ts
│ │ │ │ └── index.ts
│ │ │ ├── users
│ │ │ │ ├── handlers
│ │ │ │ │ ├── get.ts
│ │ │ │ │ └── update.ts
│ │ │ │ └── index.ts
│ │ │ ├── utils
│ │ │ │ ├── handlers
│ │ │ │ │ ├── autocomplete.ts
│ │ │ │ │ ├── generate-css.ts
│ │ │ │ │ ├── host-config.ts
│ │ │ │ │ └── link-preview.ts
│ │ │ │ └── index.ts
│ │ │ ├── variants
│ │ │ │ ├── handlers
│ │ │ │ │ ├── create.ts
│ │ │ │ │ ├── delete.ts
│ │ │ │ │ ├── list.ts
│ │ │ │ │ └── update.ts
│ │ │ │ └── index.ts
│ │ │ ├── verification
│ │ │ │ ├── handlers
│ │ │ │ │ ├── verify-email-change.ts
│ │ │ │ │ ├── verify-email.ts
│ │ │ │ │ ├── verify-magic-link.ts
│ │ │ │ │ ├── verify-password-change.ts
│ │ │ │ │ └── verify-workspace-invite.ts
│ │ │ │ └── index.ts
│ │ │ ├── versions
│ │ │ │ ├── handlers
│ │ │ │ │ ├── diff.ts
│ │ │ │ │ ├── get.ts
│ │ │ │ │ ├── list.ts
│ │ │ │ │ ├── restore.ts
│ │ │ │ │ └── update.ts
│ │ │ │ └── index.ts
│ │ │ ├── webhooks
│ │ │ │ ├── handlers
│ │ │ │ │ ├── create.ts
│ │ │ │ │ ├── delete.ts
│ │ │ │ │ ├── get.ts
│ │ │ │ │ ├── list.ts
│ │ │ │ │ ├── reveal-secret.ts
│ │ │ │ │ └── update.ts
│ │ │ │ └── index.ts
│ │ │ ├── workspace-memberships
│ │ │ │ ├── handlers
│ │ │ │ │ ├── delete.ts
│ │ │ │ │ ├── get.ts
│ │ │ │ │ ├── leave.ts
│ │ │ │ │ ├── list-members.ts
│ │ │ │ │ ├── list-workspaces.ts
│ │ │ │ │ ├── search-members.ts
│ │ │ │ │ ├── send-invite.ts
│ │ │ │ │ └── update.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── utils.ts
│ │ │ ├── workspace-settings
│ │ │ │ ├── handlers
│ │ │ │ │ ├── get.ts
│ │ │ │ │ ├── prettier-schema.ts
│ │ │ │ │ └── update.ts
│ │ │ │ └── index.ts
│ │ │ └── workspaces
│ │ │ │ ├── handlers
│ │ │ │ ├── create.ts
│ │ │ │ ├── delete.ts
│ │ │ │ ├── get.ts
│ │ │ │ └── update.ts
│ │ │ │ └── index.ts
│ │ └── server.ts
│ └── tsconfig.json
├── cli
│ ├── LICENSE
│ ├── index.mjs
│ └── package.json
├── components
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ ├── primitives
│ │ │ ├── button.tsx
│ │ │ ├── card.tsx
│ │ │ ├── checkbox.tsx
│ │ │ ├── dropdown.tsx
│ │ │ ├── fragment.tsx
│ │ │ ├── heading.tsx
│ │ │ ├── icon.tsx
│ │ │ ├── index.tsx
│ │ │ ├── input.tsx
│ │ │ ├── loader.tsx
│ │ │ ├── overlay.tsx
│ │ │ ├── select.tsx
│ │ │ └── tooltip.tsx
│ │ ├── ref.ts
│ │ └── utils.ts
│ └── tsconfig.json
├── editor
│ ├── package.json
│ ├── src
│ │ ├── code-block.ts
│ │ ├── comment.ts
│ │ ├── document.ts
│ │ ├── element.ts
│ │ ├── embed.ts
│ │ ├── heading.ts
│ │ ├── horizontal-rule.ts
│ │ ├── image.ts
│ │ ├── index.ts
│ │ ├── list-item.ts
│ │ ├── marks.ts
│ │ ├── node-input-rule.ts
│ │ ├── node-paste-rule.ts
│ │ ├── table-cell.ts
│ │ ├── table-header.ts
│ │ ├── table.ts
│ │ ├── task-item.ts
│ │ ├── unique-id.ts
│ │ └── wrapping-input-rule.ts
│ └── tsconfig.json
├── emails
│ ├── emails
│ │ ├── magic-link.tsx
│ │ ├── verify-email-change.tsx
│ │ ├── verify-email.tsx
│ │ ├── verify-password-change.tsx
│ │ └── workspace-invite.tsx
│ ├── package.json
│ ├── readme.md
│ ├── src
│ │ ├── index.ts
│ │ └── render-email.tsx
│ └── tsconfig.json
├── scripts
│ ├── development.mjs
│ └── package.json
└── sdk
│ └── javascript
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── build.config.ts
│ ├── package.json
│ ├── src
│ ├── api
│ │ ├── client.ts
│ │ ├── content-groups.ts
│ │ ├── content-pieces.ts
│ │ ├── errors.ts
│ │ ├── extension.ts
│ │ ├── index.ts
│ │ ├── profile.ts
│ │ ├── request.ts
│ │ ├── roles.ts
│ │ ├── snippets.ts
│ │ ├── tags.ts
│ │ ├── transformers.ts
│ │ ├── user-settings.ts
│ │ ├── variants.ts
│ │ ├── webhooks.ts
│ │ ├── workspace-memberships.ts
│ │ ├── workspace-settings.ts
│ │ └── workspace.ts
│ ├── astro
│ │ ├── content.astro
│ │ ├── index.ts
│ │ ├── node.astro
│ │ └── utils.ts
│ ├── extensions
│ │ └── index.ts
│ ├── transformers
│ │ ├── content-walker.ts
│ │ ├── gfm.ts
│ │ ├── html.ts
│ │ ├── index.ts
│ │ ├── input-transformers.ts
│ │ ├── output-transformers.ts
│ │ └── transformer.ts
│ └── types
│ │ └── index.d.ts
│ └── tsconfig.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── test.json
└── turbo.json
/.dockerignore:
--------------------------------------------------------------------------------
1 | **/node_modules
2 | node_modules
3 | dist
4 | npm-debug.log
5 | .gitignore
--------------------------------------------------------------------------------
/.github/assets/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/.github/assets/cover.png
--------------------------------------------------------------------------------
/.github/workflows/publish-docs.yml:
--------------------------------------------------------------------------------
1 | on: [push, workflow_dispatch]
2 |
3 | jobs:
4 | publish:
5 | runs-on: ubuntu-latest
6 | permissions:
7 | contents: read
8 | deployments: write
9 | name: Publish Vrite Docs to Cloudflare Pages
10 | env:
11 | PUBLIC_VRITE_SEARCH_TOKEN: F0OCwgqqI3aYDXehlhNBo:dt4OCRw5OsXtU_VcTB9o-
12 | PUBLIC_VRITE_SEARCH_CONTENT_GROUP: 65d5f9a25a6b29a3ac69c65c
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 | - name: PNPM
17 | uses: pnpm/action-setup@v4
18 | - name: Build
19 | run: pnpm install && pnpm run build --filter @vrite/docs
20 | - name: Publish to Cloudflare Pages
21 | uses: cloudflare/pages-action@v1
22 | with:
23 | apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
24 | accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
25 | projectName: vrite-docs
26 | directory: apps/docs/dist
27 |
--------------------------------------------------------------------------------
/.github/workflows/publish-landing-page.yml:
--------------------------------------------------------------------------------
1 | on: [push, workflow_dispatch]
2 |
3 | jobs:
4 | publish:
5 | runs-on: ubuntu-latest
6 | permissions:
7 | contents: read
8 | deployments: write
9 | name: Publish Vrite Landing Page to Cloudflare Pages
10 | env:
11 | VRITE_ACCESS_TOKEN: o3qiG1nPs_1kG17He8smz:OOdP6YHsBTUDv78dVPko5
12 | VRITE_CONTENT_GROUP_ID: 640b816da3d81d99daed3475
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 | - name: PNPM
17 | uses: pnpm/action-setup@v4
18 | - name: Build
19 | run: pnpm install && pnpm run build --filter @vrite/landing-page
20 | - name: Publish to Cloudflare Pages
21 | uses: cloudflare/pages-action@v1
22 | with:
23 | apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
24 | accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
25 | projectName: vrite-landing-page
26 | directory: apps/landing-page/dist
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # IntelliJ IDEA
2 | .idea
3 |
4 | # dependencies
5 | node_modules
6 | .pnp
7 | .pnp.js
8 |
9 | # testing
10 | coverage
11 |
12 | # next.js
13 | .next/
14 | out/
15 | build
16 | dist
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | docker.env
30 | .env
31 | .env.local
32 | .env.development.local
33 | .env.test.local
34 | .env.production.local
35 |
36 | # turbo
37 | .turbo
38 |
39 | # React Email
40 | .react-email
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [require.resolve("prettier-plugin-astro")],
3 | printWidth: 100,
4 | useTabs: false,
5 | arrowParens: "always",
6 | quoteProps: "consistent",
7 | endOfLine: "lf",
8 | trailingComma: "none",
9 | overrides: [{ files: "*.astro", options: { parser: "astro" } }]
10 | };
11 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "clicktracking",
4 | "codepen",
5 | "codesandbox",
6 | "Collab",
7 | "composables",
8 | "exitable",
9 | "hget",
10 | "Hocuspocus",
11 | "hset",
12 | "kanban",
13 | "Kanbans",
14 | "Lexo",
15 | "listitem",
16 | "OPENAI",
17 | "openapi",
18 | "outdir",
19 | "sadd",
20 | "smembers",
21 | "srem",
22 | "totp",
23 | "unsign",
24 | "Unsubscribable",
25 | "weaviate"
26 | ],
27 | "unocss.root": ["apps/web"],
28 | "typescript.tsdk": "node_modules/typescript/lib"
29 | }
30 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Reporting a Vulnerability
4 |
5 | - E-mail your findings to security@vrite.io;
6 | - Do not take advantage of the vulnerability or problem you have discovered;
7 | - Do not reveal the problem to others until it has been resolved;
8 | - Do not use attacks on physical security, social engineering, distributed denial of service, spam, or applications of third parties;
9 | - Do provide sufficient information to reproduce the problem, so we will be able to resolve it as quickly as possible. Usually, a short description of the vulnerability will be sufficient, but complex vulnerabilities may require further explanation;
10 |
--------------------------------------------------------------------------------
/apps/backend/api/.env.example:
--------------------------------------------------------------------------------
1 | PORT=4444
--------------------------------------------------------------------------------
/apps/backend/api/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine as base
2 |
3 | FROM base AS builder
4 | WORKDIR /app
5 | RUN apk add --no-cache libc6-compat && npm install -g turbo
6 |
7 | COPY . .
8 |
9 | RUN turbo prune @vrite/api --docker
10 |
11 |
12 | FROM base as installer
13 | WORKDIR /app
14 |
15 | RUN npm install -g pnpm@8.6.0
16 | RUN npm install -g turbo
17 |
18 | COPY --from=builder /app/out/json/ .
19 | COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
20 | RUN pnpm i --frozen-lockfile
21 |
22 | COPY --from=builder /app/out/full/ .
23 |
24 | RUN pnpm rebuild -r
25 | RUN turbo run build --filter=@vrite/api
26 |
27 | FROM base AS runner
28 | WORKDIR /app
29 |
30 | # Install extra dependencies
31 | RUN npm install saslprep
32 | RUN addgroup --system --gid 1001 nodejs
33 | RUN adduser --system --uid 1001 app
34 |
35 | USER app
36 | COPY --from=installer --chown=app:nodejs /app/apps/backend/api/dist .
37 | CMD node index.js
38 |
--------------------------------------------------------------------------------
/apps/backend/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vrite/api",
3 | "private": true,
4 | "source": "src/index.ts",
5 | "main": "index.ts",
6 | "scripts": {
7 | "dev": "scripts dev-node src/index.ts",
8 | "build": "scripts build-node src/index.ts",
9 | "start": "node ./dist/index.js"
10 | },
11 | "dependencies": {
12 | "@fastify/cors": "^9.0.1",
13 | "@fastify/rate-limit": "^9.1.0",
14 | "@trpc/server": "^10.45.0",
15 | "@vrite/backend": "workspace:*",
16 | "fastify": "^4.26.0",
17 | "trpc-openapi": "^1.2.0"
18 | },
19 | "devDependencies": {
20 | "@vrite/scripts": "workspace:*"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/apps/backend/api/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "esModuleInterop": true,
6 | "moduleResolution": "node",
7 | "isolatedModules": true,
8 | "strict": true,
9 | "outDir": "build",
10 | "skipLibCheck": true,
11 | "paths": {
12 | "#*": ["./src/*"]
13 | }
14 | },
15 | "include": ["src/**/*.ts"]
16 | }
17 |
--------------------------------------------------------------------------------
/apps/backend/app/.env.example:
--------------------------------------------------------------------------------
1 | PORT=3333
--------------------------------------------------------------------------------
/apps/backend/app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine as base
2 |
3 | FROM base AS builder
4 | WORKDIR /app
5 | RUN apk add --no-cache libc6-compat && npm install -g turbo
6 |
7 | COPY . .
8 |
9 | RUN turbo prune @vrite/app --docker
10 |
11 |
12 | FROM base as installer
13 | WORKDIR /app
14 |
15 | RUN npm install -g pnpm@8.6.0
16 | RUN npm install -g turbo
17 |
18 | COPY --from=builder /app/out/json/ .
19 | COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
20 | RUN pnpm i --frozen-lockfile
21 |
22 | COPY --from=builder /app/out/full/ .
23 |
24 | RUN pnpm rebuild -r
25 | RUN turbo run build --filter=@vrite/app
26 |
27 | FROM base AS runner
28 | WORKDIR /app
29 |
30 | # Install external dependencies
31 | RUN npm install saslprep sharp
32 | RUN addgroup --system --gid 1001 nodejs
33 | RUN adduser --system --uid 1001 app
34 |
35 | USER app
36 | COPY --from=installer --chown=app:nodejs /app/apps/backend/app/dist .
37 | CMD node index.js
38 |
--------------------------------------------------------------------------------
/apps/backend/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vrite/app",
3 | "private": true,
4 | "source": "src/index.ts",
5 | "main": "index.ts",
6 | "scripts": {
7 | "dev": "scripts dev-node src/index.ts",
8 | "build": "scripts build-node src/index.ts && cp -rf ../../web/dist/ ./dist/public",
9 | "start": "node ./dist/index.js"
10 | },
11 | "dependencies": {
12 | "@aws-sdk/client-s3": "^3.507.0",
13 | "@fastify/multipart": "^8.1.0",
14 | "@fastify/static": "^7.0.0",
15 | "@fastify/view": "^8.2.0",
16 | "@fastify/websocket": "^8.3.1",
17 | "@types/mime-types": "^2.1.4",
18 | "@vrite/backend": "workspace:*",
19 | "axios": "^1.6.7",
20 | "fastify": "^4.26.0",
21 | "handlebars": "^4.7.8",
22 | "mime-types": "^2.1.35",
23 | "nanoid": "^5.0.5",
24 | "sharp": "^0.33.2"
25 | },
26 | "devDependencies": {
27 | "@img/sharp-darwin-arm64": "^0.33.2",
28 | "@vrite/scripts": "workspace:*",
29 | "@vrite/web": "workspace:*"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/apps/backend/app/src/index.ts:
--------------------------------------------------------------------------------
1 | import { appService } from "./app";
2 | import { billingPlugin, createServer, webhooksPlugin } from "@vrite/backend";
3 | import {
4 | databasePlugin,
5 | pubSubPlugin,
6 | gitSyncPlugin,
7 | searchPlugin,
8 | s3Plugin,
9 | sessionPlugin,
10 | OAuthPlugin,
11 | emailPlugin
12 | } from "@vrite/backend";
13 |
14 | (async () => {
15 | const server = await createServer(async (server) => {
16 | await server.register(databasePlugin);
17 | await server.register(pubSubPlugin);
18 | await server.register(sessionPlugin).register(OAuthPlugin);
19 | await server.register(emailPlugin);
20 | await server.register(gitSyncPlugin);
21 | await server.register(searchPlugin);
22 | await server.register(webhooksPlugin);
23 | await server.register(s3Plugin);
24 | await server.register(billingPlugin);
25 | });
26 |
27 | await server.register(appService);
28 | server.listen({ host: server.config.HOST, port: server.config.PORT }, (err) => {
29 | if (err) {
30 | server.log.error(err);
31 | process.exit(1);
32 | }
33 | });
34 | })();
35 |
--------------------------------------------------------------------------------
/apps/backend/app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "esModuleInterop": true,
6 | "moduleResolution": "node",
7 | "isolatedModules": true,
8 | "strict": true,
9 | "outDir": "build",
10 | "skipLibCheck": true,
11 | "paths": {
12 | "#*": ["./src/*"]
13 | }
14 | },
15 | "include": ["src/**/*.ts"]
16 | }
17 |
--------------------------------------------------------------------------------
/apps/backend/assets/.env.example:
--------------------------------------------------------------------------------
1 | PORT=8888
--------------------------------------------------------------------------------
/apps/backend/assets/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine as base
2 |
3 | FROM base AS builder
4 | WORKDIR /app
5 | RUN apk add --no-cache libc6-compat && npm install -g turbo
6 |
7 | COPY . .
8 |
9 | RUN turbo prune @vrite/assets --docker
10 |
11 |
12 | FROM base as installer
13 | WORKDIR /app
14 |
15 | RUN npm install -g pnpm@8.6.0
16 | RUN npm install -g turbo
17 |
18 | COPY --from=builder /app/out/json/ .
19 | COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
20 | RUN pnpm i --frozen-lockfile
21 |
22 | COPY --from=builder /app/out/full/ .
23 |
24 | RUN pnpm rebuild -r
25 | RUN turbo run build --filter=@vrite/assets
26 |
27 | FROM base AS runner
28 | WORKDIR /app
29 |
30 | # Install extra dependencies
31 | RUN npm install saslprep sharp
32 | RUN addgroup --system --gid 1001 nodejs
33 | RUN adduser --system --uid 1001 app
34 |
35 | USER app
36 | COPY --from=installer --chown=app:nodejs /app/apps/backend/assets/dist .
37 | CMD node index.js
38 |
--------------------------------------------------------------------------------
/apps/backend/assets/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vrite/assets",
3 | "private": true,
4 | "source": "src/index.ts",
5 | "main": "index.ts",
6 | "scripts": {
7 | "dev": "scripts dev-node src/index.ts",
8 | "build": "scripts build-node src/index.ts",
9 | "start": "node ./dist/index.js"
10 | },
11 | "dependencies": {
12 | "@aws-sdk/client-s3": "^3.507.0",
13 | "@fastify/rate-limit": "^9.1.0",
14 | "@trpc/server": "^10.45.0",
15 | "@vrite/backend": "workspace:*",
16 | "fastify": "^4.26.0",
17 | "sharp": "^0.33.2",
18 | "trpc-openapi": "^1.2.0",
19 | "zod": "^3.22.4"
20 | },
21 | "devDependencies": {
22 | "@img/sharp-darwin-arm64": "^0.33.2",
23 | "@vrite/scripts": "workspace:*"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/apps/backend/assets/src/index.ts:
--------------------------------------------------------------------------------
1 | import { assetsService } from "./assets";
2 | import { createServer, s3Plugin } from "@vrite/backend";
3 |
4 | (async () => {
5 | const server = await createServer(async (server) => {
6 | await server.register(s3Plugin);
7 | });
8 |
9 | await server.register(assetsService);
10 |
11 | return server.listen({ host: server.config.HOST, port: server.config.PORT }, (err) => {
12 | if (err) {
13 | server.log.error(err);
14 | process.exit(1);
15 | }
16 | });
17 | })();
18 |
--------------------------------------------------------------------------------
/apps/backend/assets/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "esModuleInterop": true,
6 | "moduleResolution": "node",
7 | "isolatedModules": true,
8 | "strict": true,
9 | "outDir": "build",
10 | "skipLibCheck": true,
11 | "paths": {
12 | "#*": ["./src/*"]
13 | }
14 | },
15 | "include": ["src/**/*.ts"]
16 | }
17 |
--------------------------------------------------------------------------------
/apps/backend/collaboration/.env.example:
--------------------------------------------------------------------------------
1 | PORT=5555
--------------------------------------------------------------------------------
/apps/backend/collaboration/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine as base
2 |
3 | FROM base AS builder
4 | WORKDIR /app
5 | RUN apk add --no-cache libc6-compat && npm install -g turbo
6 |
7 | COPY . .
8 |
9 | RUN turbo prune @vrite/collaboration --docker
10 |
11 |
12 | FROM base as installer
13 | WORKDIR /app
14 |
15 | RUN npm install -g pnpm@8.6.0
16 | RUN npm install -g turbo
17 |
18 | COPY --from=builder /app/out/json/ .
19 | COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
20 | RUN pnpm i --frozen-lockfile
21 |
22 | COPY --from=builder /app/out/full/ .
23 |
24 | RUN pnpm rebuild -r
25 | RUN turbo run build --filter=@vrite/collaboration
26 |
27 | FROM base AS runner
28 | WORKDIR /app
29 |
30 | # Install extra dependencies
31 | RUN npm install saslprep
32 | RUN addgroup --system --gid 1001 nodejs
33 | RUN adduser --system --uid 1001 app
34 |
35 | USER app
36 | COPY --from=installer --chown=app:nodejs /app/apps/backend/collaboration/dist .
37 | CMD node index.js
38 |
--------------------------------------------------------------------------------
/apps/backend/collaboration/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vrite/collaboration",
3 | "private": true,
4 | "source": "src/index.ts",
5 | "main": "index.ts",
6 | "scripts": {
7 | "dev": "scripts dev-node src/index.ts",
8 | "build": "scripts build-node src/index.ts",
9 | "start": "node ./dist/index.js"
10 | },
11 | "dependencies": {
12 | "@hocuspocus/extension-database": "^2.11.3",
13 | "@hocuspocus/extension-redis": "^2.11.3",
14 | "@hocuspocus/server": "^2.11.3",
15 | "@vrite/backend": "workspace:*",
16 | "@vrite/sdk": "workspace:*",
17 | "dayjs": "^1.11.10",
18 | "fastify": "^4.26.0",
19 | "mongodb": "^6.3.0",
20 | "y-protocols": "^1.0.6",
21 | "yjs": "^13.6.11"
22 | },
23 | "devDependencies": {
24 | "@vrite/scripts": "workspace:*"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/apps/backend/collaboration/src/index.ts:
--------------------------------------------------------------------------------
1 | import { writingPlugin } from "./writing";
2 | import {
3 | OAuthPlugin,
4 | createServer,
5 | databasePlugin,
6 | pubSubPlugin,
7 | searchPlugin,
8 | sessionPlugin
9 | } from "@vrite/backend";
10 |
11 | (async () => {
12 | const server = await createServer(async (server) => {
13 | await server.register(databasePlugin);
14 | await server.register(pubSubPlugin);
15 | await server.register(sessionPlugin).register(OAuthPlugin);
16 | await server.register(searchPlugin);
17 | });
18 |
19 | await server.register(writingPlugin);
20 | })();
21 |
--------------------------------------------------------------------------------
/apps/backend/collaboration/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "esModuleInterop": true,
6 | "moduleResolution": "node",
7 | "isolatedModules": true,
8 | "strict": true,
9 | "outDir": "build",
10 | "skipLibCheck": true,
11 | "paths": {
12 | "#*": ["./src/*"]
13 | }
14 | },
15 | "include": ["src/**/*.ts"]
16 | }
17 |
--------------------------------------------------------------------------------
/apps/backend/extensions/.env.example:
--------------------------------------------------------------------------------
1 | PORT=7777
--------------------------------------------------------------------------------
/apps/backend/extensions/src/index.ts:
--------------------------------------------------------------------------------
1 | import { extensionsService } from "./extensions";
2 | import { createServer } from "@vrite/backend";
3 |
4 | (async () => {
5 | const server = await createServer();
6 |
7 | await server.register(extensionsService);
8 |
9 | return server.listen({ host: server.config.HOST, port: server.config.PORT }, (err) => {
10 | if (err) {
11 | server.log.error(err);
12 | process.exit(1);
13 | }
14 | });
15 | })();
16 |
--------------------------------------------------------------------------------
/apps/backend/extensions/src/routes/index.ts:
--------------------------------------------------------------------------------
1 | import { devRouter } from "./dev";
2 | import { hashnodeRouter } from "./hashnode";
3 | import { gptRouter } from "./gpt";
4 | import { mediumRouter } from "./medium";
5 | import { mdxRouter } from "./mdx";
6 | import { router } from "@vrite/backend";
7 |
8 | const extensionsRouter = router({
9 | dev: devRouter,
10 | hashnode: hashnodeRouter,
11 | medium: mediumRouter,
12 | gpt: gptRouter,
13 | mdx: mdxRouter
14 | });
15 |
16 | type Router = typeof extensionsRouter;
17 |
18 | export { extensionsRouter };
19 | export type { Router };
20 |
--------------------------------------------------------------------------------
/apps/backend/extensions/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "esModuleInterop": true,
6 | "moduleResolution": "node",
7 | "isolatedModules": true,
8 | "strict": true,
9 | "outDir": "build",
10 | "skipLibCheck": true,
11 | "paths": {
12 | "#*": ["./src/*"]
13 | }
14 | },
15 | "include": ["src/**/*.ts"]
16 | }
17 |
--------------------------------------------------------------------------------
/apps/backend/usage-reporting/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine as base
2 |
3 | FROM base AS builder
4 | WORKDIR /app
5 | RUN apk add --no-cache libc6-compat && npm install -g turbo
6 |
7 | COPY . .
8 |
9 | RUN turbo prune @vrite/usage-reporting --docker
10 |
11 |
12 | FROM base as installer
13 | WORKDIR /app
14 |
15 | RUN npm install -g pnpm@8.6.0
16 | RUN npm install -g turbo
17 |
18 | COPY --from=builder /app/out/json/ .
19 | COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
20 | RUN pnpm i --frozen-lockfile
21 |
22 | COPY --from=builder /app/out/full/ .
23 |
24 | RUN pnpm rebuild -r
25 | RUN turbo run build --filter=@vrite/usage-reporting
26 |
27 | FROM base AS runner
28 | WORKDIR /app
29 |
30 | # Install extra dependencies
31 | RUN npm install saslprep
32 | RUN addgroup --system --gid 1001 nodejs
33 | RUN adduser --system --uid 1001 app
34 |
35 | USER app
36 | COPY --from=installer --chown=app:nodejs /app/apps/backend/usage-reporting/dist .
37 | CMD node index.js
38 |
--------------------------------------------------------------------------------
/apps/backend/usage-reporting/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vrite/usage-reporting",
3 | "private": true,
4 | "source": "src/index.ts",
5 | "main": "index.ts",
6 | "scripts": {
7 | "build": "scripts build-node src/index.ts",
8 | "start": "node ./dist/index.js"
9 | },
10 | "dependencies": {
11 | "@fastify/cors": "^9.0.1",
12 | "@fastify/rate-limit": "^9.1.0",
13 | "@trpc/server": "^10.45.0",
14 | "@vrite/backend": "workspace:*",
15 | "fastify": "^4.26.0",
16 | "trpc-openapi": "^1.2.0"
17 | },
18 | "devDependencies": {
19 | "@vrite/scripts": "workspace:*"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/apps/backend/usage-reporting/src/index.ts:
--------------------------------------------------------------------------------
1 | import { createServer, databasePlugin, pubSubPlugin, billingPlugin } from "@vrite/backend";
2 |
3 | (async () => {
4 | await createServer(async (server) => {
5 | await server.register(databasePlugin);
6 | await server.register(pubSubPlugin);
7 | await server.register(billingPlugin);
8 |
9 | const usageLogs = await server.billing.usage.getLogs();
10 |
11 | await Promise.all(
12 | Object.keys(usageLogs).map(async (workspaceId) => {
13 | await server.billing.usage.record(workspaceId);
14 | })
15 | );
16 | process.exit(0);
17 | });
18 | })();
19 |
--------------------------------------------------------------------------------
/apps/backend/usage-reporting/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "esModuleInterop": true,
6 | "moduleResolution": "node",
7 | "isolatedModules": true,
8 | "strict": true,
9 | "outDir": "build",
10 | "skipLibCheck": true,
11 | "paths": {
12 | "#*": ["./src/*"]
13 | }
14 | },
15 | "include": ["src/**/*.ts"]
16 | }
17 |
--------------------------------------------------------------------------------
/apps/docs/.gitignore:
--------------------------------------------------------------------------------
1 | # build output
2 | dist/
3 |
4 | # dependencies
5 | node_modules/
6 |
7 | # logs
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 | pnpm-debug.log*
12 |
13 |
14 | # environment variables
15 | .env
16 | .env.production
17 |
18 | # macOS-specific files
19 | .DS_Store
--------------------------------------------------------------------------------
/apps/docs/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["astro-build.astro-vscode"],
3 | "unwantedRecommendations": []
4 | }
5 |
--------------------------------------------------------------------------------
/apps/docs/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "command": "./node_modules/.bin/astro dev",
6 | "name": "Development server",
7 | "request": "launch",
8 | "type": "node-terminal"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/apps/docs/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Vrite
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/apps/docs/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Vrite landing page
4 |
5 | This is the source code for the official landing page and blog for Vrite at https://vrite.io.
6 |
7 | The stack used:
8 |
9 | - **Astro** (SSG/SSR framework)
10 | - **Solid.js** (for all the interactive UI components)
11 | - **Vrite CMS & SDK** (powering the blog)
12 |
13 | ## Quick start
14 |
15 | First install dependencies and start the dev server:
16 |
17 | ```
18 | pnpm install
19 | pnpm dev
20 | ```
21 |
--------------------------------------------------------------------------------
/apps/docs/astro.config.mjs:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "astro/config";
2 | import solidJs from "@astrojs/solid-js";
3 | import unocss from "unocss/astro";
4 | import robotsTxt from "astro-robots-txt";
5 | import mdx from "@astrojs/mdx";
6 | import autoImport from "astro-auto-import";
7 | import icon from "astro-icon";
8 |
9 | export default defineConfig({
10 | markdown: {
11 | shikiConfig: {
12 | theme: "github-dark"
13 | }
14 | },
15 | integrations: [
16 | autoImport({
17 | imports: [
18 | {
19 | "#components/content": [
20 | "Card",
21 | "CardGrid",
22 | "Important",
23 | "Info",
24 | "EndpointCard",
25 | "RequestExample",
26 | "ResponseExample"
27 | ]
28 | }
29 | ]
30 | }),
31 | unocss({ injectReset: true }),
32 | solidJs(),
33 | robotsTxt({
34 | policy: [
35 | {
36 | userAgent: "*"
37 | }
38 | ]
39 | }),
40 | icon(),
41 | mdx()
42 | ],
43 | build: {
44 | redirects: false
45 | },
46 | site: "https://docs.vrite.io",
47 | server: {
48 | port: 3000,
49 | host: true
50 | }
51 | });
52 |
--------------------------------------------------------------------------------
/apps/docs/public/_redirects:
--------------------------------------------------------------------------------
1 | / /getting-started/introduction/
--------------------------------------------------------------------------------
/apps/docs/public/meta-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/docs/public/meta-image.png
--------------------------------------------------------------------------------
/apps/docs/public/nunito-latin-ext-variable-wghtOnly-normal.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/docs/public/nunito-latin-ext-variable-wghtOnly-normal.woff2
--------------------------------------------------------------------------------
/apps/docs/public/nunito-latin-variable-wghtOnly-normal.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/docs/public/nunito-latin-variable-wghtOnly-normal.woff2
--------------------------------------------------------------------------------
/apps/docs/src/assets/icons/discord.ts:
--------------------------------------------------------------------------------
1 | const discordIcon =
2 | "M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z";
3 |
4 | export { discordIcon };
5 |
--------------------------------------------------------------------------------
/apps/docs/src/assets/icons/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./discord";
2 | export * from "./logo";
3 |
--------------------------------------------------------------------------------
/apps/docs/src/components/content/card-grid.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import clsx from "clsx";
3 | interface Props {
4 | cols?: 2 | 3 | 4;
5 | }
6 |
7 | const { props } = Astro;
8 | ---
9 |
10 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/apps/docs/src/components/content/endpoint-card/index.ts:
--------------------------------------------------------------------------------
1 | export { default as EndpointCard } from "./endpoint-card.astro";
2 | export { default as ResponseExample } from "./response-example.astro";
3 | export { default as RequestExample } from "./request-example.astro";
4 |
--------------------------------------------------------------------------------
/apps/docs/src/components/content/endpoint-card/openapi-spec.ts:
--------------------------------------------------------------------------------
1 | import type { OASDocument } from "oas/dist/types.d.cts";
2 |
3 | let spec: OASDocument | null = null;
4 |
5 | const getOpenAPISpec = async (): Promise => {
6 | const response = await fetch("https://api.vrite.io/swagger.json");
7 |
8 | spec = await response.json();
9 |
10 | return spec as OASDocument;
11 | };
12 |
13 | export { getOpenAPISpec };
14 |
--------------------------------------------------------------------------------
/apps/docs/src/components/content/endpoint-card/request-example.astro:
--------------------------------------------------------------------------------
1 | ---
2 | interface Props {
3 | label: string;
4 | }
5 | ---
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/apps/docs/src/components/content/endpoint-card/response-example.astro:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/apps/docs/src/components/content/important.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { Card, Icon } from "#components/primitives";
3 | import { mdiAlertCircleOutline } from "@mdi/js";
4 | ---
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/apps/docs/src/components/content/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Card } from "./card.astro";
2 | export { default as CardGrid } from "./card-grid.astro";
3 | export { default as Important } from "./important.astro";
4 | export { default as Info } from "./info.astro";
5 | export * from "./endpoint-card";
6 |
--------------------------------------------------------------------------------
/apps/docs/src/components/content/info.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { Card, Icon } from "#components/primitives";
3 | import { mdiInformationSlabCircleOutline } from "@mdi/js";
4 | ---
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/apps/docs/src/components/fragments/analytics.tsx:
--------------------------------------------------------------------------------
1 | import Plausible from "plausible-tracker";
2 | import type { Component } from "solid-js";
3 |
4 | const plausibleConfig = {
5 | trackLocalhost: false,
6 | domain: "docs.vrite.io"
7 | };
8 | const trackSignUp = (): void => {
9 | const { trackEvent } = Plausible(plausibleConfig);
10 |
11 | trackEvent("sign-up");
12 | };
13 | const Analytics: Component = () => {
14 | const { trackPageview } = Plausible(plausibleConfig);
15 |
16 | trackPageview();
17 |
18 | return <>>;
19 | };
20 |
21 | export { Analytics, trackSignUp };
22 |
--------------------------------------------------------------------------------
/apps/docs/src/components/fragments/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./analytics";
2 | export { default as BaseHead } from "./base-head.astro";
3 | export * from "./header";
4 | export * from "./side-bar";
5 | export * from "./on-this-page";
6 | export * from "./svg-defs";
7 | export * from "./footer";
8 | export * from "./search-palette";
9 | export * from "./footer";
10 | export * from "./scrollbar-width";
11 |
--------------------------------------------------------------------------------
/apps/docs/src/components/fragments/scrollbar-width.tsx:
--------------------------------------------------------------------------------
1 | import { Component, createEffect } from "solid-js";
2 |
3 | const ScrollbarWidth: Component = () => {
4 | const calculateScrollbarWidth = (): void => {
5 | document.documentElement.style.setProperty(
6 | "--scrollbar-width",
7 | `${window.innerWidth - document.documentElement.clientWidth}px`
8 | );
9 | };
10 |
11 | // recalculate on resize
12 | window.addEventListener("resize", calculateScrollbarWidth, false);
13 | // recalculate on dom load
14 | document.addEventListener("DOMContentLoaded", calculateScrollbarWidth, false);
15 | // recalculate on load (assets loaded as well)
16 | window.addEventListener("load", calculateScrollbarWidth);
17 | calculateScrollbarWidth();
18 |
19 | return <>>;
20 | };
21 |
22 | export { ScrollbarWidth };
23 |
--------------------------------------------------------------------------------
/apps/docs/src/components/fragments/svg-defs.tsx:
--------------------------------------------------------------------------------
1 | import type { Component } from "solid-js";
2 |
3 | const SVGDefs: Component = () => {
4 | return (
5 |
13 | );
14 | };
15 |
16 | export { SVGDefs };
17 |
--------------------------------------------------------------------------------
/apps/docs/src/components/layouts/index.ts:
--------------------------------------------------------------------------------
1 | export { default as DefaultLayout } from "./default.astro";
2 |
--------------------------------------------------------------------------------
/apps/docs/src/components/primitives/image.tsx:
--------------------------------------------------------------------------------
1 | import clsx from "clsx";
2 | import type { Component } from "solid-js";
3 |
4 | interface ImageProps {
5 | srcLight: string;
6 | srcDark: string;
7 | alt: string;
8 | class?: string;
9 | }
10 |
11 | const Image: Component = (props) => {
12 | return (
13 |
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export { Image };
21 |
--------------------------------------------------------------------------------
/apps/docs/src/components/primitives/index.tsx:
--------------------------------------------------------------------------------
1 | export * from "@vrite/components";
2 | export * from "./image";
3 |
--------------------------------------------------------------------------------
/apps/docs/src/content/api/authentication.md:
--------------------------------------------------------------------------------
1 | ---
2 | slug: "api/authentication"
3 | title: "Authentication"
4 | ---
5 |
6 | Vrite uses **Bearer Tokens**, passed through the `Authorization` header to authenticate you for API access:
7 |
8 | ```
9 | Authorization: Bearer
10 | ```
11 |
12 | If you haven't already, you can create an authentication token for your account in [the settings](https://docs.vrite.io/usage-guide/configuring-vrite/#creating-and-updating-api-tokens).
--------------------------------------------------------------------------------
/apps/docs/src/content/api/workspace.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | slug: "api/workspace"
3 | title: "Workspace"
4 | ---
5 |
6 | In Vrite, each **workspace** has details like name or logo URL associated directly with them, for easier identification. You can change them in the [workspace settings](https://docs.vrite.io/usage-guide/configuring-vrite/#managing-workspace) panel.
7 |
8 | ## Retrieve Workspace Details
9 |
10 | Retrieves details of the workspace associated with the token.
11 |
12 |
13 |
14 | ```json
15 | {
16 | "id": "db8f177949c42b24896d4db4",
17 | "name": "string",
18 | "logo": "string",
19 | "description": "string"
20 | }
21 | ```
22 |
23 |
24 |
25 | ```javascript
26 | const client = createClient({
27 | token: ""
28 | });
29 | const result = await client.workspace.get();
30 | ```
31 |
32 |
--------------------------------------------------------------------------------
/apps/docs/src/content/config.ts:
--------------------------------------------------------------------------------
1 | import { z, defineCollection } from "astro:content";
2 |
3 | const docsCollection = defineCollection({
4 | type: "content",
5 | schema: z.object({
6 | title: z.string().optional(),
7 | description: z.string().optional()
8 | })
9 | });
10 | const apiCollection = defineCollection({
11 | type: "content",
12 | schema: z.object({
13 | title: z.string().optional(),
14 | description: z.string().optional()
15 | })
16 | });
17 | const recipesCollection = defineCollection({
18 | type: "content",
19 | schema: z.object({
20 | title: z.string().optional(),
21 | description: z.string().optional()
22 | })
23 | });
24 | const collections = {
25 | docs: docsCollection,
26 | api: apiCollection,
27 | recipes: recipesCollection
28 | };
29 |
30 | export { collections };
31 |
--------------------------------------------------------------------------------
/apps/docs/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | interface ImportMetaEnv {
4 | readonly PUBLIC_VRITE_SEARCH_TOKEN: string;
5 | readonly PUBLIC_VRITE_SEARCH_CONTENT_GROUP: string;
6 | }
7 |
8 | interface ImportMeta {
9 | readonly env: ImportMetaEnv;
10 | }
11 |
--------------------------------------------------------------------------------
/apps/docs/src/lib/state.ts:
--------------------------------------------------------------------------------
1 | import { createSignal } from "solid-js";
2 |
3 | const [menuOpened, setMenuOpened] = createSignal(false);
4 |
5 | export { menuOpened, setMenuOpened };
6 |
--------------------------------------------------------------------------------
/apps/docs/src/pages/[...slug].astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { DefaultLayout } from "#components/layouts";
3 | import { getCollection } from "astro:content";
4 |
5 | let { slug } = Astro.params;
6 |
7 | if (slug === undefined) {
8 | return Astro.redirect("/getting-started/introduction");
9 | }
10 |
11 | const { entry, type } = Astro.props;
12 | const { Content, headings } = await entry.render();
13 |
14 | export const prerender = true;
15 | export async function getStaticPaths() {
16 | const docsEntries = await getCollection("docs");
17 | const apiEntries = await getCollection("api");
18 | // const recipesEntries = await getCollection("recipes");
19 | return [
20 | ...apiEntries.map((entry) => ({
21 | params: { slug: entry.slug },
22 | props: { entry, type: "api" }
23 | })),
24 | ...docsEntries.map((entry) => ({
25 | params: { slug: entry.slug },
26 | props: { entry, type: "docs" }
27 | }))
28 | ];
29 | }
30 | ---
31 |
32 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/apps/docs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "astro/tsconfigs/strict",
3 | "compilerOptions": {
4 | "strict": true,
5 | "target": "ESNext",
6 | "module": "ESNext",
7 | "moduleResolution": "node",
8 | "allowSyntheticDefaultImports": true,
9 | "verbatimModuleSyntax": false,
10 | "esModuleInterop": true,
11 | "resolveJsonModule": true,
12 | "jsx": "preserve",
13 | "baseUrl": ".",
14 | "jsxImportSource": "solid-js",
15 | "types": ["astro/client"],
16 | "paths": {
17 | "#*": ["./src/*"]
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/apps/docs/unocss.config.js:
--------------------------------------------------------------------------------
1 | import { presetTypography } from "unocss";
2 | import {
3 | presetWind,
4 | transformerDirectives,
5 | transformerVariantGroup,
6 | transformerCompileClass
7 | } from "unocss";
8 | import { defineConfig } from "unocss/vite";
9 | import { presetForms } from "./unocss-preset-forms.config";
10 |
11 | export default defineConfig({
12 | layers: {
13 | "b1": -3,
14 | "b2": -2,
15 | "components": -1,
16 | "default": 1,
17 | "utilities": 2,
18 | "my-layer": 3
19 | },
20 | transformers: [
21 | transformerDirectives(),
22 | transformerCompileClass({ layer: "b1", trigger: ":base:" }),
23 | transformerCompileClass({ layer: "b2", trigger: ":base-2:" }),
24 | transformerVariantGroup()
25 | ],
26 | presets: [presetWind({ dark: "media" }), presetTypography(), presetForms()]
27 | });
28 |
--------------------------------------------------------------------------------
/apps/landing-page/.env.example:
--------------------------------------------------------------------------------
1 | VRITE_ACCESS_TOKEN=
2 | VRITE_CONTENT_GROUP_ID=
--------------------------------------------------------------------------------
/apps/landing-page/.gitignore:
--------------------------------------------------------------------------------
1 | # build output
2 | dist/
3 |
4 | # dependencies
5 | node_modules/
6 |
7 | # logs
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 | pnpm-debug.log*
12 |
13 |
14 | # environment variables
15 | .env
16 | .env.production
17 |
18 | # macOS-specific files
19 | .DS_Store
--------------------------------------------------------------------------------
/apps/landing-page/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org/
--------------------------------------------------------------------------------
/apps/landing-page/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["astro-build.astro-vscode"],
3 | "unwantedRecommendations": []
4 | }
5 |
--------------------------------------------------------------------------------
/apps/landing-page/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "command": "./node_modules/.bin/astro dev",
6 | "name": "Development server",
7 | "request": "launch",
8 | "type": "node-terminal"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/apps/landing-page/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Vrite
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/apps/landing-page/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Vrite landing page
4 |
5 | This is the source code for the official landing page and blog for Vrite at https://vrite.io.
6 |
7 | The stack used:
8 |
9 | - **Astro** (SSG/SSR framework)
10 | - **Solid.js** (for all the interactive UI components)
11 | - **Vrite CMS & SDK** (powering the blog)
12 |
13 | ## Quick start
14 |
15 | First install dependencies and start the dev server:
16 |
17 | ```
18 | pnpm install
19 | pnpm dev
20 | ```
21 |
--------------------------------------------------------------------------------
/apps/landing-page/astro.config.mjs:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "astro/config";
2 | import solidJs from "@astrojs/solid-js";
3 | import sitemap from "@astrojs/sitemap";
4 | import unocss from "unocss/astro";
5 | import { vritePlugin } from "@vrite/sdk/astro";
6 | import { loadEnv } from "vite";
7 | import robotsTxt from "astro-robots-txt";
8 |
9 | const { VRITE_ACCESS_TOKEN, VRITE_CONTENT_GROUP_ID, ...vars } = loadEnv(
10 | import.meta.env.MODE,
11 | process.cwd(),
12 | "VRITE_"
13 | );
14 |
15 | export default defineConfig({
16 | integrations: [
17 | unocss({ injectReset: true }),
18 | solidJs(),
19 | sitemap(),
20 | robotsTxt({
21 | policy: [
22 | {
23 | userAgent: "*",
24 | disallow: ["/frame/"]
25 | }
26 | ]
27 | }),
28 | vritePlugin({
29 | accessToken: VRITE_ACCESS_TOKEN,
30 | contentGroupId: VRITE_CONTENT_GROUP_ID
31 | })
32 | ],
33 | site: "https://vrite.io",
34 | server: {
35 | port: 3000,
36 | host: true
37 | }
38 | });
39 |
--------------------------------------------------------------------------------
/apps/landing-page/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vrite/landing-page",
3 | "type": "module",
4 | "version": "1.0.0",
5 | "private": true,
6 | "scripts": {
7 | "dev": "astro dev",
8 | "start": "astro preview",
9 | "build": "astro build",
10 | "preview": "astro preview",
11 | "astro": "astro"
12 | },
13 | "dependencies": {
14 | "@astrojs/sitemap": "^2.0.2",
15 | "@astrojs/solid-js": "^2.2.1",
16 | "@fontsource/nunito": "^5.0.8",
17 | "@mdi/js": "^7.2.96",
18 | "@types/html-to-text": "^9.0.1",
19 | "@unocss/reset": "^0.59.0",
20 | "@vrite/components": "workspace:*",
21 | "@vrite/sdk": "workspace:*",
22 | "astro": "^2.10.15",
23 | "atropos": "^2.0.2",
24 | "clsx": "^2.0.0",
25 | "date-fns": "^2.30.0",
26 | "html-to-text": "^9.0.5",
27 | "keen-slider": "^6.8.6",
28 | "plausible-tracker": "^0.3.8",
29 | "shiki": "^0.14.4",
30 | "simple-icons": "^11.5.0",
31 | "solid-js": "^1.8.11",
32 | "typescript": "^5.2.2",
33 | "unocss": "^0.59.0",
34 | "vite": "^4.4.9"
35 | },
36 | "devDependencies": {
37 | "astro-robots-txt": "^0.4.1"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/apps/landing-page/public/meta-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/public/meta-image.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/dark/customize.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/dark/customize.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/dark/editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/dark/editor.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/dark/explorer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/dark/explorer.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/dark/extensions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/dark/extensions.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/dark/hero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/dark/hero.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/dark/hybrid-editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/dark/hybrid-editor.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/dark/kanban-view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/dark/kanban-view.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/dark/roles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/dark/roles.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/dark/search-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/dark/search-small.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/dark/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/dark/search.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/dark/table-view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/dark/table-view.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/light/customize.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/light/customize.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/light/editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/light/editor.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/light/explorer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/light/explorer.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/light/extensions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/light/extensions.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/light/hero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/light/hero.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/light/hybrid-editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/light/hybrid-editor.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/light/roles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/light/roles.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/light/search-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/light/search-small.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/light/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/light/search.png
--------------------------------------------------------------------------------
/apps/landing-page/src/assets/images/light/table-view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/landing-page/src/assets/images/light/table-view.png
--------------------------------------------------------------------------------
/apps/landing-page/src/components/fragments/analytics.tsx:
--------------------------------------------------------------------------------
1 | import Plausible from "plausible-tracker";
2 | import type { Component } from "solid-js";
3 |
4 | const plausibleConfig = {
5 | trackLocalhost: false,
6 | domain: "vrite.io"
7 | };
8 | const trackSignUp = (): void => {
9 | const { trackEvent } = Plausible(plausibleConfig);
10 |
11 | trackEvent("sign-up");
12 | };
13 | const Analytics: Component = () => {
14 | const { trackPageview } = Plausible(plausibleConfig);
15 |
16 | trackPageview();
17 |
18 | return <>>;
19 | };
20 |
21 | export { Analytics, trackSignUp };
22 |
--------------------------------------------------------------------------------
/apps/landing-page/src/components/fragments/index.ts:
--------------------------------------------------------------------------------
1 | export { default as BaseHead } from "./base-head.astro";
2 | export { default as Footer } from "./footer.astro";
3 | export { default as Section } from "./section.astro";
4 | export { default as SVGDefs } from "./svg-defs.astro";
5 | export { default as SectionHeadline } from "./section-headline.astro";
6 | export * from "./observed";
7 | export * from "./analytics";
8 | export * from "./header";
9 | export * from "./parallax-card";
10 |
--------------------------------------------------------------------------------
/apps/landing-page/src/components/fragments/parallax-card.tsx:
--------------------------------------------------------------------------------
1 | import Atropos from "atropos";
2 | import clsx from "clsx";
3 | import type { ParentComponent } from "solid-js";
4 | import { Card } from "#components/primitives";
5 |
6 | interface ParallaxCardProps {
7 | containerClass?: string;
8 | class?: string;
9 | }
10 |
11 | const ParallaxCard: ParentComponent = (props) => {
12 | return (
13 | {
16 | Atropos({
17 | el: element,
18 | rotateXMax: 10,
19 | rotateYMax: 10,
20 | rotateTouch: false
21 | });
22 | }}
23 | >
24 |
25 |
26 |
32 | {props.children}
33 |
34 |
35 |
36 |
37 | );
38 | };
39 |
40 | export { ParallaxCard };
41 |
--------------------------------------------------------------------------------
/apps/landing-page/src/components/fragments/section-headline.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import clsx from "clsx";
3 | import { Observed } from "./observed";
4 |
5 | interface Props {
6 | title: string;
7 | subtitle: string;
8 | size?: "md" | "lg";
9 | }
10 | ---
11 |
12 |
18 |
24 | {Astro.props.title}
{
25 | Astro.props.subtitle
26 | }
27 |
28 |
29 |
--------------------------------------------------------------------------------
/apps/landing-page/src/components/fragments/section.astro:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/apps/landing-page/src/components/fragments/svg-defs.astro:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/apps/landing-page/src/components/primitives/image.tsx:
--------------------------------------------------------------------------------
1 | import clsx from "clsx";
2 | import type { Component } from "solid-js";
3 |
4 | interface ImageProps {
5 | srcLight: string;
6 | srcDark: string;
7 | alt: string;
8 | class?: string;
9 | }
10 |
11 | const Image: Component = (props) => {
12 | return (
13 |
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export { Image };
21 |
--------------------------------------------------------------------------------
/apps/landing-page/src/components/primitives/index.ts:
--------------------------------------------------------------------------------
1 | export * from "@vrite/components";
2 | export * from "./image";
3 |
--------------------------------------------------------------------------------
/apps/landing-page/src/components/sections/customize/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { Section, SectionHeadline } from "#components/fragments";
3 | import { FeaturesGrid } from "./features-grid";
4 | ---
5 |
6 |
16 |
--------------------------------------------------------------------------------
/apps/landing-page/src/components/sections/get-started/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { Section, SectionHeadline } from "#components/fragments";
3 | import { CTAGrid } from "./cta-grid";
4 | ---
5 |
6 |
16 |
--------------------------------------------------------------------------------
/apps/landing-page/src/components/sections/headless/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { Observed, Section, SectionHeadline } from "#components/fragments";
3 | import { FeaturesCarousel } from "./features-carousel";
4 | ---
5 |
6 |
7 |
11 |
12 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/apps/landing-page/src/components/sections/hero/scroll-indicator.tsx:
--------------------------------------------------------------------------------
1 | import clsx from "clsx";
2 | import { onMount, type Component, createSignal } from "solid-js";
3 |
4 | const ScrollIndicator: Component = () => {
5 | const [visible, setVisible] = createSignal(true);
6 | const handleScroll = (): void => {
7 | if (window.scrollY > 100) {
8 | setVisible(false);
9 | } else {
10 | setVisible(true);
11 | }
12 | };
13 |
14 | onMount(() => {
15 | document.addEventListener("scroll", handleScroll);
16 | });
17 |
18 | return (
19 |
25 | );
26 | };
27 |
28 | export { ScrollIndicator };
29 |
--------------------------------------------------------------------------------
/apps/landing-page/src/components/sections/index.ts:
--------------------------------------------------------------------------------
1 | export { default as PricingSection } from "./pricing/index.astro";
2 | export { default as HeroSection } from "./hero/index.astro";
3 | export { default as EditorSection } from "./editor/index.astro";
4 | export { default as HeadlessSection } from "./headless/index.astro";
5 | export { default as SearchSection } from "./search/index.astro";
6 | export { default as CustomizeSection } from "./customize/index.astro";
7 | export { default as ManageSection } from "./manage/index.astro";
8 | export { default as GetStartedSection } from "./get-started/index.astro";
9 |
--------------------------------------------------------------------------------
/apps/landing-page/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
--------------------------------------------------------------------------------
/apps/landing-page/src/icons/javascript.ts:
--------------------------------------------------------------------------------
1 | const javascriptIcon =
2 | "M6 3a3 3 0 0 0-3 3v12a3 3 0 0 0 3 3h12a3 3 0 0 0 3-3V6a3 3 0 0 0-3-3zm7.334 13.055c.72.58 1.438.865 2.156.858c.44 0 .778-.08 1.012-.242a.75.75 0 0 0 .341-.66a.971.971 0 0 0-.34-.748c-.235-.205-.679-.41-1.332-.616c-.784-.227-1.39-.52-1.815-.88c-.418-.36-.63-.862-.638-1.507c0-.609.264-1.118.792-1.529c.514-.41 1.17-.616 1.97-.616c1.114 0 2.009.271 2.683.814l-.77 1.199a2.597 2.597 0 0 0-.935-.462a3.211 3.211 0 0 0-.946-.165c-.38 0-.685.07-.913.209c-.227.14-.34.323-.34.55c0 .25.139.462.417.638c.28.169.756.356 1.43.561c.814.242 1.394.565 1.738.968c.345.403.517.917.517 1.54c0 .638-.245 1.188-.737 1.65c-.484.455-1.188.693-2.112.715c-1.21 0-2.222-.363-3.036-1.089zm-5.53.638c.235.147.517.22.847.22c.345 0 .63-.099.858-.297c.227-.205.341-.561.341-1.067v-5.302h1.485v5.588c-.022.865-.271 1.489-.748 1.87a2.466 2.466 0 0 1-.891.484a3.296 3.296 0 0 1-.935.143c-.55 0-1.038-.095-1.463-.286c-.455-.205-.836-.568-1.144-1.089l1.034-.847c.19.257.396.451.616.583";
3 |
4 | export { javascriptIcon };
5 |
--------------------------------------------------------------------------------
/apps/landing-page/src/icons/linkedin.ts:
--------------------------------------------------------------------------------
1 | const linkedinIcon =
2 | "M6.94 5a2 2 0 1 1-4-.002a2 2 0 0 1 4 .002M7 8.48H3V21h4zm6.32 0H9.34V21h3.94v-6.57c0-3.66 4.77-4 4.77 0V21H22v-7.93c0-6.17-7.06-5.94-8.72-2.91z";
3 |
4 | export { linkedinIcon };
5 |
--------------------------------------------------------------------------------
/apps/landing-page/src/icons/open-source.ts:
--------------------------------------------------------------------------------
1 | const openSourceIcon =
2 | "M12.001 2c5.523 0 10 4.477 10 10c0 4.13-2.504 7.676-6.077 9.201l-2.518-6.55A3 3 0 0 0 12 9a3 3 0 0 0-1.404 5.652l-2.518 6.55A10.003 10.003 0 0 1 2 12C2 6.477 6.477 2 12 2";
3 |
4 | export { openSourceIcon };
5 |
--------------------------------------------------------------------------------
/apps/landing-page/src/icons/sdk.ts:
--------------------------------------------------------------------------------
1 | const sdkIcon =
2 | "M5 21q-.825 0-1.412-.587T3 19V6.5q0-.35.113-.663t.337-.587l1.4-1.7q.225-.275.513-.413T6 3h12q.35 0 .638.138t.512.412l1.4 1.7q.225.275.338.588T21 6.5V19q0 .825-.587 1.413T19 21zm.4-15h13.2l-.85-1H6.25zm9.15 11.05l3.55-3.55l-3.55-3.55l-1.45 1.45l2.1 2.1l-2.1 2.1zm-5.05 0l1.45-1.45l-2.1-2.1l2.1-2.1L9.5 9.95L5.95 13.5z";
3 |
4 | export { sdkIcon };
5 |
--------------------------------------------------------------------------------
/apps/landing-page/src/lib/ref.ts:
--------------------------------------------------------------------------------
1 | type Ref = [() => V | null, (value: V) => void];
2 |
3 | const createRef = (initialValue?: V | null): Ref => {
4 | let ref: V | null = initialValue || null;
5 |
6 | return [
7 | () => ref,
8 | (value) => {
9 | ref = value;
10 | }
11 | ];
12 | };
13 |
14 | export { createRef };
15 | export type { Ref };
16 |
--------------------------------------------------------------------------------
/apps/landing-page/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | const validateEmail = (email: string): boolean => {
2 | if (!email) {
3 | return false;
4 | }
5 |
6 | const regex =
7 | /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
8 |
9 | return regex.test(email);
10 | };
11 |
12 | export { validateEmail };
13 |
--------------------------------------------------------------------------------
/apps/landing-page/src/pages/pricing.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { Header, Footer, BaseHead, SVGDefs } from "#components/fragments";
3 |
4 | import { PricingSection } from "#components/sections";
5 |
6 | const image = "https://vrite.io/meta-image.png";
7 | const title = "Vrite - developer content platform";
8 | const description =
9 | "Open-Source, collaborative developer content platform for documentation, technical blogs, and more.";
10 |
11 | export const prerender = true;
12 | ---
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/apps/landing-page/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "astro/tsconfigs/strict",
3 | "compilerOptions": {
4 | "strict": true,
5 | "target": "ESNext",
6 | "module": "ESNext",
7 | "moduleResolution": "node",
8 | "allowSyntheticDefaultImports": true,
9 | "esModuleInterop": true,
10 | "resolveJsonModule": true,
11 | "verbatimModuleSyntax": false,
12 | "jsx": "preserve",
13 | "baseUrl": ".",
14 | "jsxImportSource": "solid-js",
15 | "types": ["@vrite/sdk/types", "astro/client"],
16 | "paths": {
17 | "#*": ["./src/*"]
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/apps/landing-page/unocss.config.js:
--------------------------------------------------------------------------------
1 | import { presetTypography } from "unocss";
2 | import {
3 | presetWind,
4 | transformerDirectives,
5 | transformerVariantGroup,
6 | transformerCompileClass,
7 | } from "unocss";
8 | import { defineConfig } from "unocss/vite";
9 |
10 | export default defineConfig({
11 | layers: {
12 | b1: -3,
13 | b2: -2,
14 | components: -1,
15 | default: 1,
16 | utilities: 2,
17 | "my-layer": 3,
18 | },
19 | transformers: [
20 | transformerDirectives(),
21 | transformerCompileClass({ layer: "b1", trigger: ":base:" }),
22 | transformerCompileClass({ layer: "b2", trigger: ":base-2:" }),
23 | transformerVariantGroup(),
24 | ],
25 | presets: [presetWind({ dark: "media" }), presetTypography()],
26 | });
27 |
--------------------------------------------------------------------------------
/apps/web/public/icons/512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/web/public/icons/512.png
--------------------------------------------------------------------------------
/apps/web/public/icons/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/web/public/icons/favicon.png
--------------------------------------------------------------------------------
/apps/web/public/jetbrains-mono-wghtOnly-normal.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/web/public/jetbrains-mono-wghtOnly-normal.woff2
--------------------------------------------------------------------------------
/apps/web/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Vrite",
3 | "name": "Vrite",
4 | "icons": [
5 | {
6 | "src": "favicon.svg",
7 | "sizes": "48x48 72x72 96x96 128x128 256x256 512x512",
8 | "type": "image/svg+xml",
9 | "purpose": "any"
10 | }
11 | ],
12 | "id": "/",
13 | "start_url": "/",
14 | "background_color": "#F3F4F6",
15 | "display": "standalone",
16 | "scope": "/",
17 | "theme_color": "#e65100",
18 | "description": "Open-source headless Content Management System (CMS) for your programming blogs, documentation, and more."
19 | }
20 |
--------------------------------------------------------------------------------
/apps/web/public/nunito-latin-ext-variable-wghtOnly-normal.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/web/public/nunito-latin-ext-variable-wghtOnly-normal.woff2
--------------------------------------------------------------------------------
/apps/web/public/nunito-latin-variable-wghtOnly-normal.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/web/public/nunito-latin-variable-wghtOnly-normal.woff2
--------------------------------------------------------------------------------
/apps/web/src/assets/icons/api.ts:
--------------------------------------------------------------------------------
1 | const apiIcon =
2 | "M13 13c-.56.56-1.45.56-2 .01V13c-.55-.55-.55-1.44 0-1.99V11c.55-.55 1.44-.55 1.99 0H13c.55.55.55 1.45 0 2m-1-7l2.12 2.12l2.5-2.5l-3.2-3.2c-.78-.78-2.05-.78-2.83 0l-3.2 3.2l2.5 2.5zm-6 6l2.12-2.12l-2.5-2.5l-3.2 3.2c-.78.78-.78 2.05 0 2.83l3.2 3.2l2.5-2.5zm12 0l-2.12 2.12l2.5 2.5l3.2-3.2c.78-.78.78-2.05 0-2.83l-3.2-3.2l-2.5 2.5zm-6 6l-2.12-2.12l-2.5 2.5l3.2 3.2c.78.78 2.05.78 2.83 0l3.2-3.2l-2.5-2.5z";
3 |
4 | export { apiIcon };
5 |
--------------------------------------------------------------------------------
/apps/web/src/assets/icons/codesandbox.ts:
--------------------------------------------------------------------------------
1 | const codeSandboxIcon =
2 | "m 3.5304348,6.773913 8.7336672,-5 8.733667,5 0.07518,9.958334 -8.80885,5.041666 -8.7336672,-5 z m 1.7442274,2.0675 v 3.964166 l 2.7942723,1.550001 v 2.929999 l 3.3180415,1.913334 V 12.30558 Z m 13.9830568,0 -6.112314,3.464167 v 6.893333 l 3.318041,-1.913334 v -2.928333 l 2.794273,-1.550833 V 8.8405797 Z M 6.1484466,7.2747463 12.249066,10.72808 18.363886,7.2447464 15.130215,5.413913 l -2.84857,1.6208334 -2.8652781,-1.64 -3.2687557,1.8791666 z";
3 |
4 | export { codeSandboxIcon };
5 |
--------------------------------------------------------------------------------
/apps/web/src/assets/icons/discord.ts:
--------------------------------------------------------------------------------
1 | const discordIcon =
2 | "M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z";
3 |
4 | export { discordIcon };
5 |
--------------------------------------------------------------------------------
/apps/web/src/assets/icons/drag-vertical.ts:
--------------------------------------------------------------------------------
1 | const dragVerticalIcon =
2 | "M14 18a1 1 0 1 0 2 0a1 1 0 0 0-2 0m-6 0a1 1 0 1 0 2 0a1 1 0 0 0-2 0m6-6a1 1 0 1 0 2 0a1 1 0 0 0-2 0m-6 0a1 1 0 1 0 2 0a1 1 0 0 0-2 0m6-6a1 1 0 1 0 2 0a1 1 0 0 0-2 0M8 6a1 1 0 1 0 2 0a1 1 0 0 0-2 0";
3 |
4 | export { dragVerticalIcon };
5 |
--------------------------------------------------------------------------------
/apps/web/src/assets/icons/google.ts:
--------------------------------------------------------------------------------
1 | const googleIcon = `M3.064 7.51A9.996 9.996 0 0 1 12 2c2.695 0 4.959.99 6.69 2.605l-2.867 2.868C14.786 6.482 13.468 5.977 12 5.977c-2.605 0-4.81 1.76-5.595 4.123-.2.6-.314 1.24-.314 1.9 0 .66.114 1.3.314 1.9.786 2.364 2.99 4.123 5.595 4.123 1.345 0 2.49-.355 3.386-.955a4.6 4.6 0 0 0 1.996-3.018H12v-3.868h9.418c.118.654.182 1.336.182 2.045 0 3.046-1.09 5.61-2.982 7.35C16.964 21.105 14.7 22 12 22A9.996 9.996 0 0 1 2 12c0-1.614.386-3.14 1.064-4.49z`;
2 |
3 | export { googleIcon };
4 |
--------------------------------------------------------------------------------
/apps/web/src/assets/icons/hashnode.ts:
--------------------------------------------------------------------------------
1 | const hashnodeIcon =
2 | "m 4.2367541,9.0141325 c -1.6490053,1.6490585 -1.6490053,4.3226765 0,5.9717345 l 4.777378,4.777379 c 1.6490599,1.649004 4.3226769,1.649004 5.9717359,0 l 4.777378,-4.777379 c 1.649005,-1.649058 1.649005,-4.322676 0,-5.9717345 L 14.985868,4.2367546 c -1.649059,-1.6490054 -4.322676,-1.6490054 -5.9717359,0 z m 9.8533329,5.0759545 c 1.154351,-1.1543 1.154351,-3.025874 0,-4.1801735 -1.1543,-1.1543513 -3.025874,-1.1543513 -4.1801739,0 -1.1543514,1.1542995 -1.1543514,3.0258735 0,4.1801735 1.1542999,1.15435 3.0258739,1.15435 4.1801739,0 z";
3 |
4 | export { hashnodeIcon };
5 |
--------------------------------------------------------------------------------
/apps/web/src/assets/icons/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./codesandbox";
2 | export * from "./discord";
3 | export * from "./google";
4 | export * from "./hashnode";
5 | export * from "./logo";
6 | export * from "./mdx";
7 |
--------------------------------------------------------------------------------
/apps/web/src/assets/icons/mdx.ts:
--------------------------------------------------------------------------------
1 | const mdxIcon = `M.79 7.12h22.42c.436 0 .79.355.79.792v8.176a.79.79 0 0 1-.79.79H.79a.79.79 0 0 1-.79-.79V7.912a.79.79 0 0 1 .79-.791V7.12Zm2.507 7.605v-3.122l1.89 1.89L7.12 11.56v3.122h1.055v-5.67l-2.99 2.99L2.24 9.056v5.67h1.055v-.001Zm8.44-1.845l-1.474-1.473l-.746.746l2.747 2.747l2.745-2.747l-.746-.746l-1.473 1.473v-4h-1.054v4Zm10.041.987l-2.175-2.175l2.22-2.22l-.746-.746l-2.22 2.22l-2.22-2.22l-.747.746l2.22 2.22l-2.176 2.177l.746.746l2.177-2.177l2.176 2.175l.745-.746Z`;
2 |
3 | export { mdxIcon };
4 |
--------------------------------------------------------------------------------
/apps/web/src/assets/icons/sidebar-collapse.ts:
--------------------------------------------------------------------------------
1 | const sidebarCollapseIcon =
2 | "M 176.541 205.681 L 174.071 208.151 L 176.541 210.621 C 176.963 211.013 176.803 211.716 176.252 211.885 C 176.239 211.889 176.227 211.892 176.215 211.896 C 175.949 211.964 175.668 211.881 175.481 211.681 L 172.481 208.681 C 172.188 208.388 172.188 207.913 172.481 207.621 L 175.481 204.621 C 175.776 204.345 176.237 204.353 176.523 204.639 C 176.808 204.925 176.816 205.385 176.541 205.681 Z M 180.011 218.151 L 163.511 218.151 C 162.545 218.151 161.761 217.367 161.761 216.401 L 161.761 199.901 C 161.761 198.934 162.544 198.151 163.511 198.151 L 180.011 198.151 C 180.977 198.151 181.761 198.934 181.761 199.901 L 181.761 216.401 C 181.761 217.367 180.977 218.151 180.011 218.151 Z M 180.261 216.401 L 180.261 199.901 C 180.261 199.763 180.149 199.651 180.011 199.651 L 168.761 199.651 L 168.761 216.651 L 180.011 216.651 C 180.149 216.651 180.261 216.539 180.261 216.401 Z M 167.261 199.651 L 163.511 199.651 C 163.373 199.651 163.261 199.763 163.261 199.901 L 163.261 216.401 C 163.261 216.539 163.373 216.651 163.511 216.651 L 167.261 216.651 L 167.261 199.651 Z";
3 |
4 | export { sidebarCollapseIcon };
5 |
--------------------------------------------------------------------------------
/apps/web/src/components/fragments/index.tsx:
--------------------------------------------------------------------------------
1 | export * from "./mini-code-editor";
2 | export * from "./mini-editor";
3 | export * from "./input-field";
4 | export * from "./svg-defs";
5 | export * from "./scroll-shadow";
6 | export * from "./collapsible-section";
7 | export * from "./searchable-select";
8 |
--------------------------------------------------------------------------------
/apps/web/src/components/fragments/svg-defs.tsx:
--------------------------------------------------------------------------------
1 | import { Component } from "solid-js";
2 |
3 | const SVGDefs: Component = () => {
4 | return (
5 |
13 | );
14 | };
15 |
16 | export { SVGDefs };
17 |
--------------------------------------------------------------------------------
/apps/web/src/components/primitives/draggable.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/web/src/components/primitives/draggable.tsx
--------------------------------------------------------------------------------
/apps/web/src/components/primitives/droppable.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vriteio/vrite/9e40abfbbda94c07b5b727f58ea98ad50f1e1dc8/apps/web/src/components/primitives/droppable.tsx
--------------------------------------------------------------------------------
/apps/web/src/components/primitives/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./sortable";
2 | export * from "@vrite/components";
3 |
--------------------------------------------------------------------------------
/apps/web/src/context/host-config.tsx:
--------------------------------------------------------------------------------
1 | import { App, useClient } from "./client";
2 | import { ParentComponent, Show, createContext, createResource, useContext } from "solid-js";
3 |
4 | const HostConfigContext = createContext();
5 | const HostConfigProvider: ParentComponent = (props) => {
6 | const client = useClient();
7 | const [hostConfig] = createResource(() => {
8 | return client.utils.hostConfig.query();
9 | });
10 |
11 | return (
12 |
13 | {props.children}
14 |
15 | );
16 | };
17 | const useHostConfig = (): App.HostConfig => {
18 | return useContext(HostConfigContext)!;
19 | };
20 |
21 | export { HostConfigProvider, useHostConfig };
22 |
--------------------------------------------------------------------------------
/apps/web/src/context/index.tsx:
--------------------------------------------------------------------------------
1 | export * from "./client";
2 | export * from "./confirmation-modal";
3 | export * from "./notifications";
4 | export * from "./local-storage";
5 | export * from "./appearance";
6 | export * from "./authenticated-user-data";
7 | export * from "./extensions";
8 | export * from "./shared-state";
9 | export * from "./command-palette";
10 | export * from "./host-config";
11 | export * from "./content";
12 | export * from "./history";
13 | export * from "./meta";
14 |
--------------------------------------------------------------------------------
/apps/web/src/context/meta.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Accessor,
3 | ParentComponent,
4 | Setter,
5 | createContext,
6 | createSignal,
7 | onCleanup,
8 | useContext
9 | } from "solid-js";
10 | import { MetaProvider as SolidMetaProvider, Title } from "@solidjs/meta";
11 |
12 | interface MetaControllerData {
13 | metaTitle: Accessor;
14 | setMetaTitle: Setter;
15 | }
16 |
17 | const MetaControllerContext = createContext();
18 | const MetaProvider: ParentComponent = (props) => {
19 | const [metaTitle, setMetaTitleValue] = createSignal("");
20 | const setMetaTitle = (value: string | ((value: string) => string)): void => {
21 | setMetaTitleValue(value);
22 | onCleanup(() => setMetaTitleValue(""));
23 | };
24 |
25 | return (
26 |
27 | {metaTitle() || "Vrite"}
28 |
29 | {props.children}
30 |
31 |
32 | );
33 | };
34 | const useMeta = (): MetaControllerData => {
35 | return useContext(MetaControllerContext)!;
36 | };
37 |
38 | export { MetaProvider, useMeta };
39 |
--------------------------------------------------------------------------------
/apps/web/src/ee/index.ts:
--------------------------------------------------------------------------------
1 | export { SubscriptionBanner } from "./subscription-banner";
2 | export { BillingSection } from "./settings";
3 |
--------------------------------------------------------------------------------
/apps/web/src/ee/settings/billing/price-tag.tsx:
--------------------------------------------------------------------------------
1 | import clsx from "clsx";
2 | import { Component, Show } from "solid-js";
3 |
4 | interface PriceTagProps {
5 | perSeat?: boolean;
6 | price: number;
7 | text?: "soft" | "base";
8 | class?: string;
9 | }
10 |
11 | const PriceTag: Component = (props) => {
12 | const currencyFormatter = new Intl.NumberFormat("en-US", {
13 | style: "currency",
14 | currency: "USD",
15 | minimumFractionDigits: 2
16 | });
17 |
18 | return (
19 |
26 | {currencyFormatter.format(props.price)}
27 |
28 | /seat
29 |
30 | /
31 | mo.
32 |
33 | );
34 | };
35 |
36 | export { PriceTag };
37 |
--------------------------------------------------------------------------------
/apps/web/src/ee/settings/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./billing";
2 |
--------------------------------------------------------------------------------
/apps/web/src/layout/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./secured-layout";
2 |
--------------------------------------------------------------------------------
/apps/web/src/layout/toolbar/right-panel-menu.tsx:
--------------------------------------------------------------------------------
1 | import { mdiMenuClose, mdiMenuOpen } from "@mdi/js";
2 | import { Component } from "solid-js";
3 | import { IconButton, Tooltip } from "#components/primitives";
4 | import { useLocalStorage } from "#context";
5 |
6 | const RightPanelMenu: Component<{ variant?: "text" | "solid" }> = (props) => {
7 | const { storage, setStorage } = useLocalStorage();
8 | const rightPanelOpened = (): boolean => Number(storage().rightPanelWidth || 0) > 0;
9 |
10 | return (
11 |
17 | {
23 | setStorage((storage) => ({ ...storage, rightPanelWidth: rightPanelOpened() ? 0 : 375 }));
24 | }}
25 | />
26 |
27 | );
28 | };
29 |
30 | export { RightPanelMenu };
31 |
--------------------------------------------------------------------------------
/apps/web/src/lib/code-editor/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./format";
2 | export * from "./suggest-language";
3 |
--------------------------------------------------------------------------------
/apps/web/src/lib/editor/extensions/auto-dir.ts:
--------------------------------------------------------------------------------
1 | import { Extension } from "@tiptap/core";
2 |
3 | const AutoDir = Extension.create({
4 | name: "AutoDir",
5 | addGlobalAttributes() {
6 | return [
7 | {
8 | types: ["heading", "paragraph", "bulletList", "orderedList", "blockquote", "taskList"],
9 | attributes: {
10 | autoDir: {
11 | renderHTML: () => ({
12 | dir: "auto"
13 | }),
14 | parseHTML: (element) => element.dir || "auto"
15 | }
16 | }
17 | }
18 | ];
19 | }
20 | });
21 |
22 | export { AutoDir };
23 |
--------------------------------------------------------------------------------
/apps/web/src/lib/editor/extensions/block-action-menu/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./plugin";
2 | export * from "./component";
3 |
--------------------------------------------------------------------------------
/apps/web/src/lib/editor/extensions/code-block/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./node";
2 |
--------------------------------------------------------------------------------
/apps/web/src/lib/editor/extensions/comment-menu/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./plugin";
2 | export * from "./comment-card";
3 | export * from "./comment-input";
4 |
--------------------------------------------------------------------------------
/apps/web/src/lib/editor/extensions/element/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./node";
2 | export * from "./view-manager";
3 |
--------------------------------------------------------------------------------
/apps/web/src/lib/editor/extensions/embed/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./node";
2 |
--------------------------------------------------------------------------------
/apps/web/src/lib/editor/extensions/embed/node.ts:
--------------------------------------------------------------------------------
1 | import { EmbedView } from "./view";
2 | import { SolidNodeViewRenderer } from "@vrite/tiptap-solid";
3 | import { Embed as BaseEmbed, EmbedAttributes } from "@vrite/editor";
4 |
5 | const Embed = BaseEmbed.extend({
6 | addNodeView() {
7 | return SolidNodeViewRenderer(EmbedView);
8 | }
9 | });
10 |
11 | export { Embed };
12 | export type { EmbedAttributes };
13 |
--------------------------------------------------------------------------------
/apps/web/src/lib/editor/extensions/image/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./node";
2 |
--------------------------------------------------------------------------------
/apps/web/src/lib/editor/extensions/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./code-block";
2 | export * from "./embed";
3 | export * from "./image";
4 | export * from "./link-preview";
5 | export * from "./slash-menu";
6 | export * from "./collab-cursor";
7 | export * from "./collaboration";
8 | export * from "./shortcuts";
9 | export * from "./placeholder";
10 | export * from "./draggable-text";
11 | export * from "./block-action-menu";
12 | export * from "./comment-menu";
13 | export * from "./block-paste";
14 | export * from "./table-menu";
15 | export * from "./element";
16 | export * from "./auto-dir";
17 | export * from "./autocomplete";
18 | export * from "./diff";
19 | export { ElementMenuPlugin } from "./xml-node-menu";
20 |
--------------------------------------------------------------------------------
/apps/web/src/lib/editor/extensions/link-preview/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./wrapper";
2 |
--------------------------------------------------------------------------------
/apps/web/src/lib/editor/extensions/placeholder.ts:
--------------------------------------------------------------------------------
1 | import { Extension } from "@tiptap/core";
2 | import { Plugin, PluginKey } from "@tiptap/pm/state";
3 | import { DecorationSet, Decoration } from "@tiptap/pm/view";
4 |
5 | const Placeholder = Extension.create({
6 | name: "placeholder",
7 | addOptions() {
8 | return {
9 | placeholder: "Write something..."
10 | };
11 | },
12 | addProseMirrorPlugins() {
13 | const { options } = this;
14 |
15 | return [
16 | new Plugin({
17 | key: new PluginKey("placeholder"),
18 | props: {
19 | decorations(state) {
20 | const { doc } = state;
21 |
22 | if (
23 | doc.childCount == 1 &&
24 | doc.firstChild?.type.name === "paragraph" &&
25 | doc.firstChild?.content.size == 0
26 | ) {
27 | return DecorationSet.create(doc, [
28 | Decoration.node(0, 2, {
29 | "data-placeholder": options.placeholder,
30 | "class": "is-empty"
31 | })
32 | ]);
33 | }
34 | }
35 | }
36 | })
37 | ];
38 | }
39 | });
40 |
41 | export { Placeholder };
42 |
--------------------------------------------------------------------------------
/apps/web/src/lib/editor/extensions/slash-menu/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./plugin";
2 |
--------------------------------------------------------------------------------
/apps/web/src/lib/editor/extensions/table-menu/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./plugin";
2 |
--------------------------------------------------------------------------------
/apps/web/src/lib/editor/extensions/xml-node-menu/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./plugin";
2 |
--------------------------------------------------------------------------------
/apps/web/src/lib/editor/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./extensions";
2 | export * from "./editing";
3 |
--------------------------------------------------------------------------------
/apps/web/src/lib/extensions/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./extension-view-renderer";
2 | export * from "./sandbox";
3 | export * from "./component-renderer";
4 |
--------------------------------------------------------------------------------
/apps/web/src/lib/utils/breakpoints.ts:
--------------------------------------------------------------------------------
1 | import { createMediaQuery } from "@solid-primitives/media";
2 |
3 | const breakpoints = { md: createMediaQuery("(min-width: 768px)") };
4 |
5 | export { breakpoints };
6 |
--------------------------------------------------------------------------------
/apps/web/src/lib/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./general";
2 | export * from "./ref";
3 | export * from "./validate";
4 | export * from "./embeds";
5 | export * from "./tags";
6 | export * from "./selection";
7 | export * from "./breakpoints";
8 | export * from "./upload-file";
9 |
--------------------------------------------------------------------------------
/apps/web/src/lib/utils/ref.ts:
--------------------------------------------------------------------------------
1 | type Ref = [() => V, (value: V) => void];
2 |
3 | const createRef = (initialValue: V): Ref => {
4 | let ref = initialValue;
5 |
6 | return [
7 | () => ref,
8 | (value) => {
9 | ref = value;
10 | }
11 | ];
12 | };
13 |
14 | export { createRef };
15 | export type { Ref };
16 |
--------------------------------------------------------------------------------
/apps/web/src/lib/utils/tags.tsx:
--------------------------------------------------------------------------------
1 | import { App } from "#context";
2 |
3 | const tagColorClasses: Record = {
4 | gray: ":base: border-gray-500 bg-gray-500 text-gray-500 dark:border-gray-400 dark:bg-gray-400 dark:text-gray-400",
5 | red: ":base: border-red-500 bg-red-500 text-red-500",
6 | pink: ":base: border-pink-500 bg-pink-500 text-pink-500",
7 | fuchsia: ":base: border-fuchsia-500 bg-fuchsia-500 text-fuchsia-500",
8 | orange: ":base: border-orange-500 bg-orange-500 text-orange-500",
9 | amber: ":base: border-amber-500 bg-amber-500 text-amber-500",
10 | purple: ":base: border-purple-500 bg-purple-500 text-purple-500",
11 | indigo: ":base: border-indigo-500 bg-indigo-500 text-indigo-500",
12 | blue: ":base: border-blue-500 bg-blue-500 text-blue-500",
13 | cyan: ":base: border-cyan-500 bg-cyan-500 text-cyan-500",
14 | green: ":base: border-green-500 bg-green-500 text-green-500",
15 | teal: ":base: border-teal-500 bg-teal-500 text-teal-500",
16 | lime: ":base: border-lime-500 bg-lime-500 text-lime-500",
17 | emerald: ":base: border-emerald-500 bg-emerald-500 text-emerald-500"
18 | };
19 |
20 | export { tagColorClasses };
21 |
--------------------------------------------------------------------------------
/apps/web/src/lib/utils/upload-file.ts:
--------------------------------------------------------------------------------
1 | const uploadFile = async (file: File): Promise => {
2 | if (file && file.type.includes("image")) {
3 | const formData = new FormData();
4 |
5 | formData.append("file", file);
6 |
7 | const response = await fetch(`/upload`, {
8 | method: "POST",
9 | body: formData
10 | });
11 | const { key } = await response.json();
12 |
13 | return `${window.env.PUBLIC_ASSETS_URL}/${key}`;
14 | }
15 |
16 | return null;
17 | };
18 |
19 | export { uploadFile };
20 |
--------------------------------------------------------------------------------
/apps/web/src/styles/index.ts:
--------------------------------------------------------------------------------
1 | import "@unocss/reset/tailwind.css";
2 | import "./styles.css";
3 | import "virtual:uno.css";
4 | import "./styles.scss";
5 |
--------------------------------------------------------------------------------
/apps/web/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | interface PublicEnv {
2 | PUBLIC_APP_URL: string;
3 | PUBLIC_API_URL: string;
4 | PUBLIC_COLLAB_URL: string;
5 | PUBLIC_ASSETS_URL: string;
6 | PUBLIC_POSTHOG_TOKEN: string;
7 | PUBLIC_DISABLE_ANALYTICS: boolean;
8 | }
9 | interface ImportMetaEnv extends PublicEnv {}
10 | interface Window {
11 | env: PublicEnv;
12 | }
13 |
--------------------------------------------------------------------------------
/apps/web/src/views/auth/error-messages.tsx:
--------------------------------------------------------------------------------
1 | import { JSX } from "solid-js";
2 |
3 | const errorMessages: Record = {
4 | invalidEmail: "Incorrect email address",
5 | invalidCredentials: "Invalid credentials",
6 | emailNotVerified: "Email not verified",
7 | magicLinkAlreadySent: "Wait 1 min before requesting a new magic link",
8 | totpTokenInvalid: "Invalid 2FA code",
9 | resourceNotFound: "User not found",
10 | alreadyExists: "User already exists",
11 | disposableEmail: (
12 | <>
13 | Disposable email addresses are not allowed.
14 |
15 | If you believe this is a mistake, please contact us at{" "}
16 |
17 | support@vrite.io
18 |
19 | .
20 | >
21 | )
22 | };
23 | const getErrorMessage = (reason: string): JSX.Element => {
24 | return errorMessages[reason] || "Error";
25 | };
26 |
27 | export { getErrorMessage };
28 |
--------------------------------------------------------------------------------
/apps/web/src/views/auth/index.tsx:
--------------------------------------------------------------------------------
1 | export * from "./view";
2 |
--------------------------------------------------------------------------------
/apps/web/src/views/content-piece/description.tsx:
--------------------------------------------------------------------------------
1 | import { mdiClose, mdiTextBoxPlusOutline } from "@mdi/js";
2 | import { Component, Show } from "solid-js";
3 | import { Tooltip, IconButton, Heading } from "#components/primitives";
4 | import { MiniEditor } from "#components/fragments";
5 |
6 | interface ContentPieceDescriptionProps {
7 | initialDescription: string;
8 | editable?: boolean;
9 | setDescription(description: string | null): void;
10 | }
11 |
12 | const ContentPieceDescription: Component = (props) => {
13 | return (
14 |
15 | {
24 | props.setDescription(editor.getHTML());
25 | }}
26 | />
27 |
28 | );
29 | };
30 |
31 | export { ContentPieceDescription };
32 |
--------------------------------------------------------------------------------
/apps/web/src/views/content-piece/sections/custom-data.tsx:
--------------------------------------------------------------------------------
1 | import { Component } from "solid-js";
2 | import { MiniCodeEditor } from "#components/fragments";
3 | import { useNotifications } from "#context";
4 |
5 | interface CustomDataSectionProps {
6 | customData: Record;
7 | inEditor?: boolean;
8 | editable?: boolean;
9 | setCustomData(customData: Record): void;
10 | }
11 |
12 | const CustomDataSection: Component = (props) => {
13 | const { notify } = useNotifications();
14 | const handleSave = (code: string): void => {
15 | try {
16 | const json = JSON.parse(code);
17 |
18 | json["$schema"] = undefined;
19 | props.setCustomData(json);
20 | } catch (error) {
21 | notify({ text: "Couldn't save custom data", type: "error" });
22 | }
23 | };
24 |
25 | return (
26 |
33 | );
34 | };
35 |
36 | export { CustomDataSection };
37 |
--------------------------------------------------------------------------------
/apps/web/src/views/content-piece/sections/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./details";
2 | export * from "./custom-data";
3 | export * from "./extensions";
4 |
--------------------------------------------------------------------------------
/apps/web/src/views/content-piece/title.tsx:
--------------------------------------------------------------------------------
1 | import { Component } from "solid-js";
2 | import { MiniEditor } from "#components/fragments";
3 |
4 | interface ContentPieceTitleProps {
5 | initialTitle: string;
6 | editable?: boolean;
7 | setTitle(title: string): void;
8 | }
9 |
10 | const ContentPieceTitle: Component = (props) => {
11 | return (
12 | {
17 | props.setTitle(editor.getText());
18 | }}
19 | class="!text-2xl font-semibold !leading-7"
20 | placeholder="Title"
21 | />
22 | );
23 | };
24 |
25 | export { ContentPieceTitle };
26 |
--------------------------------------------------------------------------------
/apps/web/src/views/editor/index.tsx:
--------------------------------------------------------------------------------
1 | export * from "./content-piece-editor";
2 | export * from "./snippet-editor";
3 | export * from "./version-editor";
4 | export * from "./diff-editor";
5 |
--------------------------------------------------------------------------------
/apps/web/src/views/editor/menus/floating/index.tsx:
--------------------------------------------------------------------------------
1 | import { SolidEditor } from "@vrite/tiptap-solid";
2 | import { Component, createSignal } from "solid-js";
3 | import { mdiSlashForward } from "@mdi/js";
4 | import { IconButton, Tooltip } from "#components/primitives";
5 |
6 | interface BubbleMenuProps {
7 | editor: SolidEditor;
8 | opened: boolean;
9 | }
10 |
11 | const FloatingMenu: Component = (props) => {
12 | const [visible, setVisible] = createSignal(false);
13 |
14 | return (
15 |
22 | {
24 | setVisible(false);
25 | props.editor.chain().insertContent("/").focus().run();
26 | }}
27 | class="h-8 w-8 bg-gray-50 border-2 border-gray-200 hover:border-gray-300 dark:border-gray-900 hover:dark:border-gray-700"
28 | path={mdiSlashForward}
29 | text="soft"
30 | />
31 |
32 | );
33 | };
34 |
35 | export { FloatingMenu };
36 |
--------------------------------------------------------------------------------
/apps/web/src/views/editor/menus/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./bubble";
2 | export * from "./floating";
3 | export * from "./link-preview";
4 |
--------------------------------------------------------------------------------
/apps/web/src/views/extensions/extension-icon.tsx:
--------------------------------------------------------------------------------
1 | import { ExtensionSpec } from "@vrite/sdk/extensions";
2 | import clsx from "clsx";
3 | import { Component, Show } from "solid-js";
4 |
5 | interface ExtensionIconProps {
6 | class?: string;
7 | spec: ExtensionSpec;
8 | }
9 |
10 | const ExtensionIcon: Component = (props) => {
11 | return (
12 | <>
13 |
14 |
18 |
19 |
20 |
24 |
25 | >
26 | );
27 | };
28 |
29 | export { ExtensionIcon };
30 |
--------------------------------------------------------------------------------
/apps/web/src/views/git/provider-configuration-view/index.tsx:
--------------------------------------------------------------------------------
1 | import { GitHubConfigurationView } from "./github";
2 | import { Component, Match, Switch } from "solid-js";
3 | import { App } from "#context";
4 |
5 | interface ProviderConfigurationViewProps {
6 | providerName: string;
7 | gitData: App.GitData | null;
8 | close(): void;
9 | setActionComponent(component: Component<{}> | null): void;
10 | setOpenedProvider(provider: string): void;
11 | }
12 |
13 | const ProviderConfigurationView: Component = (props) => {
14 | return (
15 |
16 |
17 |
22 |
23 |
24 | );
25 | };
26 |
27 | export { ProviderConfigurationView };
28 |
--------------------------------------------------------------------------------
/apps/web/src/views/git/providers.ts:
--------------------------------------------------------------------------------
1 | import { mdiGithub } from "@mdi/js";
2 |
3 | interface Provider {
4 | label: string;
5 | name: string;
6 | icon: string;
7 | }
8 |
9 | const providers: Provider[] = [
10 | {
11 | label: "GitHub",
12 | name: "github",
13 | icon: mdiGithub
14 | }
15 | ];
16 |
17 | export { providers };
18 |
--------------------------------------------------------------------------------
/apps/web/src/views/settings/index.tsx:
--------------------------------------------------------------------------------
1 | export * from "./view";
2 |
--------------------------------------------------------------------------------
/apps/web/src/views/settings/webhooks/events.ts:
--------------------------------------------------------------------------------
1 | import { App } from "#context";
2 |
3 | const webhookEvents: Array<{ label: string; value: App.WebhookEventName }> = [
4 | { label: "Content piece updated", value: "contentPieceUpdated" },
5 | { label: "New content piece added", value: "contentPieceAdded" },
6 | { label: "Content piece removed", value: "contentPieceRemoved" },
7 | { label: "New content group added", value: "contentGroupAdded" },
8 | { label: "Content group removed", value: "contentGroupRemoved" },
9 | { label: "Content group moved", value: "contentGroupMoved" },
10 | { label: "New member invited", value: "memberInvited" },
11 | { label: "New member added", value: "memberAdded" },
12 | { label: "Member removed", value: "memberRemoved" }
13 | ];
14 |
15 | export { webhookEvents };
16 |
--------------------------------------------------------------------------------
/apps/web/src/views/snippets/snippets-context.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Accessor,
3 | ParentComponent,
4 | Setter,
5 | createContext,
6 | createSignal,
7 | useContext
8 | } from "solid-js";
9 |
10 | interface SnippetsMenuContextData {
11 | renaming: Accessor;
12 | loading: Accessor;
13 | setRenaming: Setter;
14 | setLoading: Setter;
15 | }
16 |
17 | const SnippetsMenuDataContext = createContext();
18 | const SnippetsMenuDataProvider: ParentComponent = (props) => {
19 | const [renaming, setRenaming] = createSignal("");
20 | const [loading, setLoading] = createSignal("");
21 |
22 | return (
23 |
31 | {props.children}
32 |
33 | );
34 | };
35 | const useSnippetsMenuData = (): SnippetsMenuContextData => {
36 | return useContext(SnippetsMenuDataContext)!;
37 | };
38 |
39 | export { SnippetsMenuDataProvider, useSnippetsMenuData };
40 |
--------------------------------------------------------------------------------
/apps/web/src/views/workspaces/index.tsx:
--------------------------------------------------------------------------------
1 | export * from "./view";
2 |
--------------------------------------------------------------------------------
/apps/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "target": "ESNext",
5 | "module": "ESNext",
6 | "moduleResolution": "node",
7 | "allowSyntheticDefaultImports": true,
8 | "esModuleInterop": true,
9 | "resolveJsonModule": true,
10 | "jsx": "preserve",
11 | "jsxImportSource": "solid-js",
12 | "types": ["vite/client", "@vrite/extensions"],
13 | "baseUrl": ".",
14 | "paths": {
15 | "#*": ["./src/*", "../../packages/backend/src/*"]
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/apps/web/unocss.config.js:
--------------------------------------------------------------------------------
1 | import { presetForms } from "./unocss-preset-forms.config";
2 | import {
3 | presetTypography,
4 | presetWind,
5 | transformerDirectives,
6 | transformerVariantGroup,
7 | transformerCompileClass
8 | } from "unocss";
9 | import { defineConfig } from "unocss/vite";
10 |
11 | export default defineConfig({
12 | layers: {
13 | "b1": -3,
14 | "b2": -2,
15 | "components": -1,
16 | "default": 1,
17 | "utilities": 2,
18 | "my-layer": 3
19 | },
20 | transformers: [
21 | transformerDirectives(),
22 | transformerCompileClass({ layer: "b1", trigger: ":base:" }),
23 | transformerCompileClass({ layer: "b2", trigger: ":base-2:" }),
24 | transformerVariantGroup()
25 | ],
26 | presets: [presetWind(), presetTypography(), presetForms()],
27 | theme: {
28 | colors: {
29 | primary: "var(--color-primary)",
30 | secondary: "var(--color-secondary)"
31 | }
32 | }
33 | });
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vrite",
3 | "version": "0.4.4",
4 | "license": "AGPL-3.0-only",
5 | "private": true,
6 | "workspaces": [
7 | "apps/*",
8 | "packages/*"
9 | ],
10 | "scripts": {
11 | "build": "dotenv -- turbo run build --env-mode=loose",
12 | "publish": "turbo run publish",
13 | "start": "dotenv -- turbo run start --env-mode=loose",
14 | "dev": "dotenv -- turbo run dev --env-mode=loose",
15 | "format": "prettier --write \"**/*.{ts,tsx,md}\""
16 | },
17 | "devDependencies": {
18 | "@typescript-eslint/eslint-plugin": "^6.2.0",
19 | "@typescript-eslint/parser": "^6.2.0",
20 | "eslint": "^8.45.0",
21 | "eslint-config-prettier": "^8.8.0",
22 | "eslint-config-xtrict": "^3.0.1",
23 | "eslint-plugin-import": "^2.27.5",
24 | "eslint-plugin-solid": "^0.12.1",
25 | "prettier": "^3.0.2",
26 | "prettier-plugin-astro": "^0.11.0",
27 | "turbo": "^2.0.3",
28 | "typescript": "^5.1.6"
29 | },
30 | "engines": {
31 | "node": ">=18.14.1"
32 | },
33 | "dependencies": {
34 | "dotenv-cli": "^7.2.1"
35 | },
36 | "packageManager": "pnpm@8.4.0"
37 | }
38 |
--------------------------------------------------------------------------------
/packages/backend/src/collections/content-groups.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { Collection, Db, ObjectId } from "mongodb";
3 | import { UnderscoreID, zodId } from "#lib/mongo";
4 |
5 | const contentGroup = z.object({
6 | id: zodId().describe("ID of the content group"),
7 | name: z.string().describe("Name of the content group"),
8 | ancestors: z
9 | .array(zodId())
10 | .describe("IDs of the content group's ancestors - from furthest to closest"),
11 | descendants: z.array(zodId()).describe("IDs of the content group's direct descendants")
12 | });
13 |
14 | interface ContentGroup
15 | extends Omit, "id" | "ancestors" | "descendants"> {
16 | id: ID;
17 | ancestors: ID[];
18 | descendants: ID[];
19 | }
20 |
21 | interface FullContentGroup extends ContentGroup {
22 | workspaceId: ID;
23 | }
24 |
25 | const getContentGroupsCollection = (
26 | db: Db
27 | ): Collection>> => {
28 | return db.collection("content-groups");
29 | };
30 |
31 | export { contentGroup, getContentGroupsCollection };
32 | export type { ContentGroup, FullContentGroup };
33 |
--------------------------------------------------------------------------------
/packages/backend/src/collections/content-variants.ts:
--------------------------------------------------------------------------------
1 | import { Binary, Collection, Db, ObjectId } from "mongodb";
2 | import { UnderscoreID } from "#lib/mongo";
3 |
4 | interface ContentVariant {
5 | contentPieceId: ID;
6 | variantId: ID;
7 | content?: Binary;
8 | id: ID;
9 | }
10 |
11 | interface FullContentVariant extends ContentVariant {}
12 |
13 | const getContentVariantsCollection = (
14 | db: Db
15 | ): Collection>> => {
16 | return db.collection("content-variants");
17 | };
18 |
19 | export { getContentVariantsCollection };
20 | export type { ContentVariant, FullContentVariant };
21 |
--------------------------------------------------------------------------------
/packages/backend/src/collections/content-versions.ts:
--------------------------------------------------------------------------------
1 | import { Binary, Collection, Db, ObjectId } from "mongodb";
2 | import { UnderscoreID } from "#lib/mongo";
3 |
4 | interface ContentVersion {
5 | contentPieceId: ID;
6 | versionId: ID;
7 | variantId?: ID;
8 | content: Binary;
9 | id: ID;
10 | }
11 |
12 | interface FullContentVersion extends ContentVersion {
13 | expiresAt?: ID extends ObjectId ? Date : string;
14 | }
15 |
16 | const getContentVersionsCollection = (
17 | db: Db
18 | ): Collection>> => {
19 | return db.collection("content-versions");
20 | };
21 |
22 | export { getContentVersionsCollection };
23 | export type { ContentVersion, FullContentVersion };
24 |
--------------------------------------------------------------------------------
/packages/backend/src/collections/contents.ts:
--------------------------------------------------------------------------------
1 | import { Binary, Collection, Db, ObjectId } from "mongodb";
2 | import { UnderscoreID } from "#lib/mongo";
3 |
4 | interface Contents {
5 | contentPieceId: ID;
6 | content?: Binary;
7 | id: ID;
8 | }
9 | interface FullContents extends Contents {}
10 |
11 | const getContentsCollection = (db: Db): Collection>> => {
12 | return db.collection("contents");
13 | };
14 |
15 | export { getContentsCollection };
16 | export type { Contents, FullContents };
17 |
--------------------------------------------------------------------------------
/packages/backend/src/collections/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./content-pieces";
2 | export * from "./contents";
3 | export * from "./roles";
4 | export * from "./tags";
5 | export * from "./tokens";
6 | export * from "./user-settings";
7 | export * from "./users";
8 | export * from "./webhooks";
9 | export * from "./workspace-memberships";
10 | export * from "./workspace-settings";
11 | export * from "./workspaces";
12 | export * from "./extensions";
13 | export * from "./comment-threads";
14 | export * from "./comments";
15 | export * from "./content-piece-variants";
16 | export * from "./content-variants";
17 | export * from "./variants";
18 | export * from "./content-groups";
19 | export * from "./git-data";
20 | export * from "./variants";
21 | export * from "./transformers";
22 | export * from "./snippets";
23 | export * from "./snippet-contents";
24 | export * from "./content-versions";
25 | export * from "./versions";
26 |
--------------------------------------------------------------------------------
/packages/backend/src/collections/snippet-contents.ts:
--------------------------------------------------------------------------------
1 | import { Binary, Collection, Db, ObjectId } from "mongodb";
2 | import { UnderscoreID } from "#lib/mongo";
3 |
4 | interface SnippetContent {
5 | snippetId: ID;
6 | content?: Binary;
7 | id: ID;
8 | }
9 | interface FullSnippetContent extends SnippetContent {}
10 |
11 | const getSnippetContentsCollection = (
12 | db: Db
13 | ): Collection>> => {
14 | return db.collection("snippet-contents");
15 | };
16 |
17 | export { getSnippetContentsCollection };
18 | export type { SnippetContent, FullSnippetContent };
19 |
--------------------------------------------------------------------------------
/packages/backend/src/collections/snippets.ts:
--------------------------------------------------------------------------------
1 | import { Collection, Db, ObjectId } from "mongodb";
2 | import { z } from "zod";
3 | import { UnderscoreID, zodId } from "#lib/mongo";
4 |
5 | const snippet = z.object({
6 | id: zodId().describe("ID of the snippet"),
7 | name: z.string().describe("Name of the snippet")
8 | });
9 |
10 | interface Snippet
11 | extends Omit, "id"> {
12 | id: ID;
13 | }
14 |
15 | interface FullSnippet extends Snippet {
16 | workspaceId: ID;
17 | }
18 |
19 | const getSnippetsCollection = (db: Db): Collection>> => {
20 | return db.collection("snippets");
21 | };
22 |
23 | export { snippet, getSnippetsCollection };
24 | export type { Snippet, FullSnippet };
25 |
--------------------------------------------------------------------------------
/packages/backend/src/collections/transformers.ts:
--------------------------------------------------------------------------------
1 | import { Collection, Db, ObjectId } from "mongodb";
2 | import { z } from "zod";
3 | import { UnderscoreID, zodId } from "#lib/mongo";
4 |
5 | const transformer = z.object({
6 | id: zodId().describe("ID of the transformer"),
7 | label: z.string().describe("Label assigned to the transformer").min(1).max(50),
8 | input: z.string().describe("URL of the input transformer").url(),
9 | output: z.string().describe("URL of the output transformer").url(),
10 | maxBatchSize: z.number().describe("Maximum batch size for the transformer").min(1).max(1000)
11 | });
12 |
13 | interface Transformer
14 | extends Omit, "id"> {
15 | id: ID;
16 | }
17 | interface FullTransformer extends Transformer {
18 | workspaceId: ID;
19 | extensionId?: ID;
20 | }
21 |
22 | const getTransformersCollection = (db: Db): Collection>> => {
23 | return db.collection("transformers");
24 | };
25 |
26 | export { transformer, getTransformersCollection };
27 | export type { Transformer };
28 |
--------------------------------------------------------------------------------
/packages/backend/src/collections/variants.ts:
--------------------------------------------------------------------------------
1 | import { Collection, Db, ObjectId } from "mongodb";
2 | import { z } from "zod";
3 | import { UnderscoreID, zodId } from "#lib/mongo";
4 |
5 | const variant = z.object({
6 | id: zodId().describe("ID of the variant"),
7 | label: z.string().describe("Label assigned to the variant").min(1).max(50),
8 | description: z.string().describe("Description of the variant").optional(),
9 | key: z
10 | .string()
11 | .describe("Short, unique key for the variant (for use with the API)")
12 | .min(1)
13 | .max(50)
14 | });
15 |
16 | interface Variant
17 | extends Omit, "id"> {
18 | id: ID;
19 | }
20 | interface FullVariant extends Variant {
21 | workspaceId: ID;
22 | }
23 |
24 | const getVariantsCollection = (db: Db): Collection>> => {
25 | return db.collection("variants");
26 | };
27 |
28 | export { variant, getVariantsCollection };
29 | export type { Variant, FullVariant };
30 |
--------------------------------------------------------------------------------
/packages/backend/src/ee/billing/index.ts:
--------------------------------------------------------------------------------
1 | import { billingPlugin } from "./plugin";
2 | import { billingRouter } from "./route";
3 |
4 | export { billingPlugin, billingRouter };
5 |
--------------------------------------------------------------------------------
/packages/backend/src/ee/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./billing";
2 |
--------------------------------------------------------------------------------
/packages/backend/src/events/extension.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from "@trpc/server/observable";
2 | import { ContextObject, Extension } from "#collections";
3 | import { Context } from "#lib/context";
4 | import { createEventPublisher, createEventSubscription } from "#lib/pub-sub";
5 |
6 | type ExtensionEvent =
7 | | { action: "delete"; data: { id: string }; userId: string }
8 | | { action: "create"; data: Extension & { id: string }; userId: string }
9 | | { action: "update"; data: { id: string; config: ContextObject }; userId: string };
10 |
11 | const publishExtensionEvent = createEventPublisher((workspaceId) => {
12 | return `extensions:${workspaceId}`;
13 | });
14 | const subscribeToExtensionEvents = (
15 | ctx: Context,
16 | workspaceId: string
17 | ): Observable => {
18 | return createEventSubscription(ctx, `extensions:${workspaceId}`);
19 | };
20 |
21 | export { publishExtensionEvent, subscribeToExtensionEvents };
22 | export type { ExtensionEvent };
23 |
--------------------------------------------------------------------------------
/packages/backend/src/events/git-data.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from "@trpc/server/observable";
2 | import { GitData } from "#collections";
3 | import { Context } from "#lib/context";
4 | import { createEventPublisher, createEventSubscription } from "#lib/pub-sub";
5 |
6 | type GitDataEvent =
7 | | {
8 | action: "configure";
9 | data: GitData;
10 | }
11 | | {
12 | action: "update";
13 | data: Partial;
14 | }
15 | | {
16 | action: "reset";
17 | data: {};
18 | };
19 |
20 | const publishGitDataEvent = createEventPublisher((workspaceId) => {
21 | return `gitData:${workspaceId}`;
22 | });
23 | const subscribeToGitDataEvents = (
24 | ctx: Context,
25 | workspaceId: string
26 | ): Observable => {
27 | return createEventSubscription(ctx, `gitData:${workspaceId}`);
28 | };
29 |
30 | export { publishGitDataEvent, subscribeToGitDataEvents };
31 | export type { GitDataEvent };
32 |
--------------------------------------------------------------------------------
/packages/backend/src/events/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./comment";
2 | export * from "./content-group";
3 | export * from "./content-piece";
4 | export * from "./extension";
5 | export * from "./tag";
6 | export * from "./token";
7 | export * from "./transformer";
8 | export * from "./user-settings";
9 | export * from "./user";
10 | export * from "./variant";
11 | export * from "./workspace-membership";
12 | export * from "./webhook";
13 | export * from "./workspace-settings";
14 | export * from "./workspace";
15 | export * from "./git-data";
16 | export * from "./role";
17 | export * from "./snippet";
18 | export * from "./versions";
19 |
--------------------------------------------------------------------------------
/packages/backend/src/events/role.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from "@trpc/server/observable";
2 | import { Role } from "#collections";
3 | import { Context } from "#lib/context";
4 | import { createEventPublisher, createEventSubscription } from "#lib/pub-sub";
5 |
6 | type RoleEvent =
7 | | {
8 | action: "create";
9 | data: Role;
10 | }
11 | | { action: "update"; data: Partial & { id: string } }
12 | | { action: "delete"; data: { id: string; newRole: Role } };
13 |
14 | const publishRoleEvent = createEventPublisher((workspaceId) => `roles:${workspaceId}`);
15 | const subscribeToRoleEvents = (
16 | ctx: Context,
17 | workspaceId: string
18 | ): Observable => {
19 | return createEventSubscription(ctx, `roles:${workspaceId}`);
20 | };
21 |
22 | export { publishRoleEvent, subscribeToRoleEvents };
23 | export type { RoleEvent };
24 |
--------------------------------------------------------------------------------
/packages/backend/src/events/snippet.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from "@trpc/server/observable";
2 | import { Context } from "#lib/context";
3 | import { createEventPublisher, createEventSubscription } from "#lib/pub-sub";
4 | import { FullSnippet } from "#collections";
5 |
6 | type SnippetEvent =
7 | | { action: "delete"; userId: string; data: { id: string } }
8 | | { action: "create"; userId: string; data: FullSnippet }
9 | | {
10 | action: "update";
11 | userId: string;
12 | data: Partial & { id: string };
13 | };
14 |
15 | const publishSnippetEvent = createEventPublisher((contentGroupId) => {
16 | return `snippets:${contentGroupId}`;
17 | });
18 | const subscribeToSnippetEvents = (
19 | ctx: Context,
20 | workspaceId: string
21 | ): Observable => {
22 | return createEventSubscription(ctx, `snippets:${workspaceId}`);
23 | };
24 |
25 | export { publishSnippetEvent, subscribeToSnippetEvents };
26 | export type { SnippetEvent };
27 |
--------------------------------------------------------------------------------
/packages/backend/src/events/tag.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from "@trpc/server/observable";
2 | import { Context } from "#lib/context";
3 | import { createEventPublisher, createEventSubscription } from "#lib/pub-sub";
4 | import { Tag } from "#collections";
5 |
6 | type TagEvent =
7 | | { action: "create"; data: Tag }
8 | | { action: "update"; data: Partial & { id: string } }
9 | | {
10 | action: "delete";
11 | data: { id: string };
12 | };
13 |
14 | const publishTagEvent = createEventPublisher((workspaceId) => `tags:${workspaceId}`);
15 | const subscribeToTagEvents = (ctx: Context, workspaceId: string): Observable => {
16 | return createEventSubscription(ctx, `tags:${workspaceId}`);
17 | };
18 |
19 | export { publishTagEvent, subscribeToTagEvents };
20 | export type { TagEvent };
21 |
--------------------------------------------------------------------------------
/packages/backend/src/events/token.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from "@trpc/server/observable";
2 | import { Context } from "#lib/context";
3 | import { createEventPublisher, createEventSubscription } from "#lib/pub-sub";
4 | import { Token } from "#collections";
5 |
6 | type TokenEvent =
7 | | {
8 | action: "create";
9 | data: Token;
10 | }
11 | | { action: "update"; data: Partial & { id: string } }
12 | | { action: "delete"; data: { id: string } };
13 |
14 | const publishTokenEvent = createEventPublisher(
15 | (workspaceId) => `tokens:${workspaceId}`
16 | );
17 | const subscribeToTokenEvents = (
18 | ctx: Context,
19 | workspaceId: string
20 | ): Observable => {
21 | return createEventSubscription(ctx, `tokens:${workspaceId}`);
22 | };
23 |
24 | export { publishTokenEvent, subscribeToTokenEvents };
25 | export type { TokenEvent };
26 |
--------------------------------------------------------------------------------
/packages/backend/src/events/transformer.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from "@trpc/server/observable";
2 | import { Context } from "#lib/context";
3 | import { createEventPublisher, createEventSubscription } from "#lib/pub-sub";
4 | import { Transformer } from "#collections";
5 |
6 | type TransformerEvent =
7 | | {
8 | action: "create";
9 | data: Transformer & { id: string };
10 | }
11 | | {
12 | action: "delete";
13 | data: { id: string };
14 | };
15 |
16 | const publishTransformerEvent = createEventPublisher(
17 | (workspaceId) => `transformers:${workspaceId}`
18 | );
19 | const subscribeToTransformerEvents = (
20 | ctx: Context,
21 | workspaceId: string
22 | ): Observable => {
23 | return createEventSubscription(ctx, `transformers:${workspaceId}`);
24 | };
25 |
26 | export { publishTransformerEvent, subscribeToTransformerEvents };
27 | export type { TransformerEvent };
28 |
--------------------------------------------------------------------------------
/packages/backend/src/events/user-settings.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from "@trpc/server/observable";
2 | import { AppearanceSettings } from "#collections";
3 | import { Context } from "#lib/context";
4 | import { createEventPublisher, createEventSubscription } from "#lib/pub-sub";
5 |
6 | type UserSettingsEvent = { action: "update"; data: Partial };
7 |
8 | const publishUserSettingsEvent = createEventPublisher(
9 | (userId) => `userSettings:${userId}`
10 | );
11 | const subscribeToUserSettingsEvents = (
12 | ctx: Context,
13 | userId: string
14 | ): Observable => {
15 | return createEventSubscription(ctx, `userSettings:${userId}`);
16 | };
17 |
18 | export { publishUserSettingsEvent, subscribeToUserSettingsEvents };
19 | export type { UserSettingsEvent };
20 |
--------------------------------------------------------------------------------
/packages/backend/src/events/user.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from "@trpc/server/observable";
2 | import { VerificationDetails, Profile } from "#collections";
3 | import { Context } from "#lib/context";
4 | import { createEventPublisher, createEventSubscription } from "#lib/pub-sub";
5 |
6 | type UserEvent = {
7 | action: "update";
8 | data: Partial & { id: string } & Partial;
9 | };
10 |
11 | const publishUserEvent = createEventPublisher((userId: string) => `user:${userId}`);
12 | const subscribeToUserEvents = (ctx: Context, userId: string): Observable => {
13 | return createEventSubscription(ctx, `user:${userId}`);
14 | };
15 |
16 | export { publishUserEvent, subscribeToUserEvents };
17 | export type { UserEvent };
18 |
--------------------------------------------------------------------------------
/packages/backend/src/events/variant.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from "@trpc/server/observable";
2 | import { Variant } from "#collections";
3 | import { Context } from "#lib/context";
4 | import { createEventPublisher, createEventSubscription } from "#lib/pub-sub";
5 |
6 | type VariantEvent =
7 | | {
8 | action: "create";
9 | data: Variant & { id: string };
10 | }
11 | | {
12 | action: "update";
13 | data: Partial & { id: string };
14 | }
15 | | {
16 | action: "delete";
17 | data: { id: string };
18 | };
19 |
20 | const publishVariantEvent = createEventPublisher(
21 | (workspaceId) => `variants:${workspaceId}`
22 | );
23 | const subscribeToVariantEvents = (
24 | ctx: Context,
25 | workspaceId: string
26 | ): Observable => {
27 | return createEventSubscription(ctx, `variants:${workspaceId}`);
28 | };
29 |
30 | export { publishVariantEvent, subscribeToVariantEvents };
31 | export type { VariantEvent };
32 |
--------------------------------------------------------------------------------
/packages/backend/src/events/versions.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from "@trpc/server/observable";
2 | import { Context } from "#lib/context";
3 | import { createEventPublisher, createEventSubscription } from "#lib/pub-sub";
4 | import { FullVersionWithAdditionalData } from "#collections";
5 |
6 | type VersionEvent =
7 | | { action: "create"; userId: string; data: FullVersionWithAdditionalData }
8 | | {
9 | action: "update";
10 | userId: string;
11 | data: Partial & { id: string };
12 | };
13 |
14 | const publishVersionEvent = createEventPublisher((contentGroupId) => {
15 | return `versions:${contentGroupId}`;
16 | });
17 | const subscribeToVersionEvents = (
18 | ctx: Context,
19 | workspaceId: string
20 | ): Observable => {
21 | return createEventSubscription(ctx, `versions:${workspaceId}`);
22 | };
23 |
24 | export { publishVersionEvent, subscribeToVersionEvents };
25 | export type { VersionEvent };
26 |
--------------------------------------------------------------------------------
/packages/backend/src/events/webhook.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from "@trpc/server/observable";
2 | import { Context } from "#lib/context";
3 | import { createEventPublisher, createEventSubscription } from "#lib/pub-sub";
4 | import { Webhook } from "#collections";
5 |
6 | type WebhookEvent =
7 | | {
8 | action: "create";
9 | data: Webhook & { id: string };
10 | }
11 | | {
12 | action: "update";
13 | data: Partial & { id: string };
14 | }
15 | | {
16 | action: "delete";
17 | data: { id: string };
18 | };
19 |
20 | const publishWebhookEvent = createEventPublisher((workspaceId) => {
21 | return `webhooks:${workspaceId}`;
22 | });
23 | const subscribeToWebhookEvents = (
24 | ctx: Context,
25 | workspaceId: string
26 | ): Observable => {
27 | return createEventSubscription(ctx, `webhooks:${workspaceId}`);
28 | };
29 |
30 | export { publishWebhookEvent, subscribeToWebhookEvents };
31 | export type { WebhookEvent };
32 |
--------------------------------------------------------------------------------
/packages/backend/src/events/workspace-settings.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from "@trpc/server/observable";
2 | import { WorkspaceSettings } from "#collections";
3 | import { Context } from "#lib/context";
4 | import { createEventPublisher, createEventSubscription } from "#lib/pub-sub";
5 |
6 | type WorkspaceSettingsEvent = {
7 | action: "update";
8 | data: Partial>;
9 | };
10 |
11 | const publishWorkspaceSettingsEvent = createEventPublisher(
12 | (workspaceId) => {
13 | return `workspaceSettings:${workspaceId}`;
14 | }
15 | );
16 | const subscribeToWorkspaceSettingsEvents = (
17 | ctx: Context,
18 | workspaceId: string
19 | ): Observable => {
20 | return createEventSubscription(ctx, `workspaceSettings:${workspaceId}`);
21 | };
22 |
23 | export { publishWorkspaceSettingsEvent, subscribeToWorkspaceSettingsEvents };
24 | export type { WorkspaceSettingsEvent };
25 |
--------------------------------------------------------------------------------
/packages/backend/src/events/workspace.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from "@trpc/server/observable";
2 | import { Workspace } from "#collections";
3 | import { Context } from "#lib/context";
4 | import { createEventPublisher, createEventSubscription } from "#lib/pub-sub";
5 |
6 | type WorkspaceEvent =
7 | | { action: "update"; data: Partial & { id: string } }
8 | | { action: "delete"; data: { id: string } };
9 |
10 | const publishWorkspaceEvent = createEventPublisher((workspaceId) => {
11 | return `workspace:${workspaceId}`;
12 | });
13 | const subscribeToWorkspaceEvents = (
14 | ctx: Context,
15 | workspaceId: string
16 | ): Observable => {
17 | return createEventSubscription(ctx, `workspace:${workspaceId}`);
18 | };
19 |
20 | export { publishWorkspaceEvent, subscribeToWorkspaceEvents };
21 | export type { WorkspaceEvent };
22 |
--------------------------------------------------------------------------------
/packages/backend/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./lib/auth";
2 | export * from "./lib/errors";
3 | export * from "./lib/plugin";
4 | export * from "./lib/context";
5 | export * from "./lib/trpc";
6 | export * from "./lib/mongo";
7 | export * from "./lib/session";
8 | export * from "./lib/content-processing/conversions";
9 | export * from "./lib/git-sync";
10 | export * from "./lib/utils";
11 | export * from "./plugins";
12 | export * from "./collections";
13 | export * from "./events";
14 | export * from "./routes";
15 | export * from "./server";
16 | export * from "./ee";
17 |
--------------------------------------------------------------------------------
/packages/backend/src/lib/content-processing/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./conversions";
2 | export * from "./diff";
3 |
--------------------------------------------------------------------------------
/packages/backend/src/lib/context.ts:
--------------------------------------------------------------------------------
1 | import { CreateFastifyContextOptions } from "@trpc/server/adapters/fastify";
2 | import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
3 | import { Db } from "mongodb";
4 |
5 | interface FastifyContext {
6 | fastify: FastifyInstance;
7 | req: FastifyRequest;
8 | res: FastifyReply;
9 | }
10 | interface Context extends FastifyContext {
11 | db: Db;
12 | }
13 |
14 | const createFastifyContext = (
15 | { req, res }: CreateFastifyContextOptions,
16 | fastify: FastifyInstance
17 | ): FastifyContext => {
18 | return { req, res, fastify };
19 | };
20 | const createContext = (
21 | { req, res }: CreateFastifyContextOptions,
22 | fastify: FastifyInstance
23 | ): Context => {
24 | const { db } = fastify.mongo;
25 |
26 | if (!db) {
27 | throw new Error("Database not connected");
28 | }
29 |
30 | return {
31 | ...createFastifyContext({ req, res }, fastify),
32 | db
33 | };
34 | };
35 |
36 | export { createContext, createFastifyContext };
37 | export type { FastifyContext, Context };
38 |
--------------------------------------------------------------------------------
/packages/backend/src/lib/git-sync/index.ts:
--------------------------------------------------------------------------------
1 | import { useGitHubProvider } from "./providers/github";
2 | import { UseGitProvider } from "./provider";
3 | import { ObjectId } from "mongodb";
4 | import { FullGitData } from "#collections";
5 | import { AuthenticatedContext } from "#lib/middleware";
6 | import { UnderscoreID } from "#lib/mongo";
7 |
8 | const useGitProvider = (
9 | ctx: AuthenticatedContext,
10 | gitData?: UnderscoreID> | null
11 | ): ReturnType | null => {
12 | if (gitData && gitData.github) {
13 | return useGitHubProvider(ctx, gitData);
14 | }
15 |
16 | return null;
17 | };
18 |
19 | export { useGitProvider };
20 | export * from "./process-content";
21 | export * from "./provider";
22 |
--------------------------------------------------------------------------------
/packages/backend/src/lib/git-sync/providers/github/index.ts:
--------------------------------------------------------------------------------
1 | import { commit } from "./commit";
2 | import { initialSync } from "./initial-sync";
3 | import { pull } from "./pull";
4 | import { defineGitProvider } from "../../provider";
5 |
6 | const useGitHubProvider = defineGitProvider({
7 | getData: ({ gitData }) => {
8 | return gitData.github!;
9 | },
10 | commit,
11 | initialSync,
12 | pull
13 | });
14 |
15 | export { useGitHubProvider };
16 |
--------------------------------------------------------------------------------
/packages/backend/src/lib/git-sync/providers/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./github";
2 |
--------------------------------------------------------------------------------
/packages/backend/src/lib/hash.ts:
--------------------------------------------------------------------------------
1 | import { promisify } from "node:util";
2 | import crypto from "node:crypto";
3 |
4 | const pbkdf2 = promisify(crypto.pbkdf2);
5 | const generateSalt = async (): Promise => {
6 | return crypto.randomBytes(16).toString("hex");
7 | };
8 | const hashValue = async (value: string, salt: string): Promise => {
9 | const hashBuffer = await pbkdf2(value, salt, 100, 64, "sha512");
10 |
11 | return hashBuffer.toString("hex");
12 | };
13 | const verifyValue = async (value: string, salt: string, hash: string): Promise => {
14 | const hashBuffer = await pbkdf2(value, salt, 100, 64, "sha512");
15 |
16 | return hashBuffer.toString("hex") === hash;
17 | };
18 |
19 | export { generateSalt, hashValue, verifyValue };
20 |
--------------------------------------------------------------------------------
/packages/backend/src/lib/host-config.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | const hostConfig = z.object({
4 | githubOAuth: z.boolean().describe("Whether GitHub OAuth is enabled"),
5 | githubApp: z.boolean().describe("Whether GitHub App is configured for Git sync"),
6 | resend: z.boolean().describe("Whether Resend is configured for email"),
7 | smtp: z.boolean().describe("Whether SMTP is configured for email"),
8 | search: z.boolean().describe("Whether Weaviate is configured for search"),
9 | aiSearch: z.boolean().describe("Whether Weaviate and OpenAI is configured for Q&A search"),
10 | extensions: z.boolean().describe("Whether extensions are enabled"),
11 | analytics: z.boolean().describe("Whether analytics are enabled"),
12 | billing: z.boolean().describe("Whether subscription billing is enabled")
13 | });
14 |
15 | interface HostConfig extends z.infer {}
16 |
17 | export { hostConfig };
18 | export type { HostConfig };
19 |
--------------------------------------------------------------------------------
/packages/backend/src/lib/mongo.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | type UnderscoreID> = Omit & { _id: T["id"] };
4 |
5 | const zodId = (): z.ZodString => z.string().regex(/^[a-f\d]{24}$/i, "invalid id");
6 | const zodIdList = (): z.ZodString => {
7 | return z.string().regex(/^[a-f\d]{24}(,[a-f\d]{24})*$/i, "invalid id list");
8 | };
9 |
10 | export { zodId, zodIdList };
11 | export type { UnderscoreID };
12 |
--------------------------------------------------------------------------------
/packages/backend/src/lib/plugin.ts:
--------------------------------------------------------------------------------
1 | import { FastifyPluginAsync } from "fastify";
2 |
3 | type Plugin = { [key: symbol]: boolean } & FastifyPluginAsync;
4 |
5 | const createPlugin = (plugin: FastifyPluginAsync): Plugin => {
6 | const typedPlugin = plugin as Plugin;
7 |
8 | typedPlugin[Symbol.for("skip-override")] = true;
9 |
10 | return typedPlugin;
11 | };
12 |
13 | export { createPlugin };
14 |
--------------------------------------------------------------------------------
/packages/backend/src/lib/trpc.ts:
--------------------------------------------------------------------------------
1 | import { Context } from "./context";
2 | import { CustomError } from "./errors";
3 | import { HostConfig } from "./host-config";
4 | import { initTRPC } from "@trpc/server";
5 | import { OpenApiMeta } from "trpc-openapi";
6 | import { TokenPermission, Permission } from "#collections";
7 |
8 | type Meta = OpenApiMeta & {
9 | permissions?: { session?: Permission[]; token?: TokenPermission[] };
10 | requiredConfig?: Array;
11 | requiredSubscriptionPlan?: "personal" | "team";
12 | };
13 |
14 | const { router, middleware, procedure } = initTRPC
15 | .meta()
16 | .context()
17 | .create({
18 | errorFormatter(errorData) {
19 | const error = errorData.error as CustomError;
20 | const { shape } = errorData;
21 |
22 | return {
23 | ...shape,
24 | data: {
25 | ...shape.data,
26 | cause: error.causeData
27 | }
28 | };
29 | }
30 | });
31 |
32 | export { router, middleware, procedure };
33 | export type { Meta };
34 |
--------------------------------------------------------------------------------
/packages/backend/src/plugins/git-sync/handlers/content-group-created.ts:
--------------------------------------------------------------------------------
1 | import { createGitSyncHandler } from "../utils";
2 | import { ObjectId } from "mongodb";
3 | import { FullContentGroup } from "#collections";
4 | import { UnderscoreID } from "#lib/mongo";
5 |
6 | const handleContentGroupCreated = createGitSyncHandler<{
7 | contentGroup: UnderscoreID>;
8 | }>(async ({ directories, records }, data) => {
9 | const newDirectories = [...directories];
10 | const directAncestor = data.contentGroup.ancestors[data.contentGroup.ancestors.length - 1];
11 |
12 | if (directAncestor) {
13 | const ancestorDirectory = newDirectories.find((directory) => {
14 | return directory.contentGroupId.equals(directAncestor);
15 | });
16 |
17 | if (ancestorDirectory) {
18 | newDirectories.push({
19 | contentGroupId: data.contentGroup._id,
20 | path: ancestorDirectory.path
21 | .split("/")
22 | .concat(data.contentGroup.name || `${data.contentGroup._id}`)
23 | .filter(Boolean)
24 | .join("/")
25 | });
26 | }
27 | }
28 |
29 | return { directories: newDirectories, records };
30 | });
31 |
32 | export { handleContentGroupCreated };
33 |
--------------------------------------------------------------------------------
/packages/backend/src/plugins/git-sync/handlers/content-group-removed.ts:
--------------------------------------------------------------------------------
1 | import { createGitSyncHandler } from "../utils";
2 | import { ObjectId } from "mongodb";
3 | import { FullContentGroup } from "#collections";
4 | import { UnderscoreID } from "#lib/mongo";
5 |
6 | const handleContentGroupRemoved = createGitSyncHandler<{
7 | contentGroup: UnderscoreID>;
8 | }>(async ({ directories, records }, data) => {
9 | const existingDirectory = directories.find((directory) => {
10 | return directory.contentGroupId.equals(data.contentGroup._id);
11 | });
12 |
13 | if (!existingDirectory) return { directories, records };
14 |
15 | const directoryPath = existingDirectory.path;
16 |
17 | return {
18 | directories: directories.filter((directory) => {
19 | return !directory.path.startsWith(directoryPath);
20 | }),
21 | records: records.map((record) => {
22 | if (record.path.startsWith(directoryPath)) {
23 | return {
24 | ...record,
25 | currentHash: ""
26 | };
27 | }
28 |
29 | return record;
30 | })
31 | };
32 | });
33 |
34 | export { handleContentGroupRemoved };
35 |
--------------------------------------------------------------------------------
/packages/backend/src/plugins/git-sync/handlers/content-piece-removed.ts:
--------------------------------------------------------------------------------
1 | import { createGitSyncHandler } from "../utils";
2 | import { ObjectId } from "mongodb";
3 | import { FullContentPiece } from "#collections";
4 | import { UnderscoreID } from "#lib/mongo";
5 |
6 | const handleContentPieceRemoved = createGitSyncHandler<{
7 | contentPiece: UnderscoreID>;
8 | }>(async ({ directories, records }, data) => {
9 | return {
10 | directories,
11 | records: records.map((record) => {
12 | if (record.contentPieceId.equals(data.contentPiece._id)) {
13 | return {
14 | ...record,
15 | currentHash: ""
16 | };
17 | }
18 |
19 | return record;
20 | })
21 | };
22 | });
23 |
24 | export { handleContentPieceRemoved };
25 |
--------------------------------------------------------------------------------
/packages/backend/src/plugins/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./database";
2 | export * from "./email";
3 | export * from "./oauth";
4 | export * from "./pub-sub";
5 | export * from "./s3";
6 | export * from "./session";
7 | export * from "./trpc";
8 | export * from "./git-sync";
9 | export * from "./search";
10 | export * from "./host-config";
11 | export * from "./webhooks";
12 |
--------------------------------------------------------------------------------
/packages/backend/src/plugins/s3.ts:
--------------------------------------------------------------------------------
1 | import { S3Client, CreateBucketCommand, HeadBucketCommand } from "@aws-sdk/client-s3";
2 | import { createPlugin } from "#lib/plugin";
3 |
4 | declare module "fastify" {
5 | interface FastifyInstance {
6 | s3: S3Client;
7 | }
8 | }
9 |
10 | const s3Plugin = createPlugin(async (fastify) => {
11 | const s3Client = new S3Client({
12 | endpoint: fastify.config.S3_ENDPOINT,
13 | forcePathStyle: fastify.config.S3_FORCE_PATH_STYLE,
14 | region: fastify.config.S3_REGION,
15 | credentials: {
16 | accessKeyId: fastify.config.S3_ACCESS_KEY,
17 | secretAccessKey: fastify.config.S3_SECRET_KEY
18 | }
19 | });
20 |
21 | try {
22 | await s3Client.send(new HeadBucketCommand({ Bucket: fastify.config.S3_BUCKET }));
23 | } catch (error) {
24 | await s3Client.send(new CreateBucketCommand({ Bucket: fastify.config.S3_BUCKET }));
25 | }
26 |
27 | fastify.decorate("s3", s3Client);
28 | });
29 |
30 | export { s3Plugin };
31 |
--------------------------------------------------------------------------------
/packages/backend/src/plugins/trpc.ts:
--------------------------------------------------------------------------------
1 | import { fastifyTRPCPlugin } from "@trpc/server/adapters/fastify";
2 | import { FastifyReply, FastifyRequest } from "fastify";
3 | import { appRouter } from "#routes";
4 | import { createPlugin } from "#lib/plugin";
5 | import { createContext } from "#lib/context";
6 |
7 | const trpcPlugin = createPlugin(async (fastify) => {
8 | await fastify.register(fastifyTRPCPlugin, {
9 | prefix: "/api/v1",
10 | useWSS: true,
11 | trpcOptions: {
12 | router: appRouter,
13 | createContext({ req, res }: { req: FastifyRequest; res: FastifyReply }) {
14 | return createContext({ req, res }, fastify);
15 | }
16 | }
17 | });
18 | });
19 |
20 | export { trpcPlugin };
21 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/auth/handlers/init-two-factor.ts:
--------------------------------------------------------------------------------
1 | import { totpConfig } from "../utils";
2 | import { z } from "zod";
3 | import { nanoid } from "nanoid";
4 | import * as OTPAuth from "otpauth";
5 | import { AuthenticatedContext } from "#lib/middleware";
6 | import { getUsersCollection } from "#collections";
7 | import { errors } from "#lib/errors";
8 |
9 | const outputSchema = z.object({
10 | totp: z.string().describe("The TOTP URI")
11 | });
12 | const handler = async (ctx: AuthenticatedContext): Promise> => {
13 | const usersCollection = getUsersCollection(ctx.db);
14 | const user = await usersCollection.findOne({
15 | _id: ctx.auth.userId
16 | });
17 |
18 | if (!user) throw errors.notFound("user");
19 |
20 | const totpSecret = nanoid();
21 |
22 | await usersCollection.updateOne({ _id: user._id }, { $set: { totpSecret } });
23 |
24 | const totp = new OTPAuth.TOTP({
25 | ...totpConfig,
26 | label: user.username,
27 | secret: totpSecret
28 | });
29 |
30 | return { totp: totp.toString() };
31 | };
32 |
33 | export { outputSchema, handler };
34 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/auth/handlers/is-signed-in.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { processAuth } from "#lib/auth";
3 | import { Context } from "#lib/context";
4 |
5 | const outputSchema = z.object({
6 | isSignedIn: z.boolean().describe("Whether the user is signed in")
7 | });
8 | const handler = async (ctx: Context): Promise> => {
9 | const auth = await processAuth(ctx);
10 |
11 | return {
12 | isSignedIn: Boolean(auth)
13 | };
14 | };
15 |
16 | export { outputSchema, handler };
17 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/auth/handlers/logout.ts:
--------------------------------------------------------------------------------
1 | import { Context } from "#lib/context";
2 | import { deleteSession, getSessionId } from "#lib/session";
3 |
4 | const handler = async (ctx: Context): Promise => {
5 | const sessionId = await getSessionId(ctx, "refreshToken");
6 |
7 | if (sessionId) {
8 | deleteSession(ctx, sessionId);
9 | }
10 | };
11 |
12 | export { handler };
13 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/auth/handlers/refresh-token.ts:
--------------------------------------------------------------------------------
1 | import { Context } from "#lib/context";
2 | import { errors } from "#lib/errors";
3 | import { getSessionId, refreshSession } from "#lib/session";
4 |
5 | const handler = async (ctx: Context): Promise => {
6 | let sessionId = await getSessionId(ctx, "accessToken");
7 |
8 | if (sessionId) {
9 | return;
10 | }
11 |
12 | sessionId = await getSessionId(ctx, "refreshToken");
13 |
14 | if (!sessionId) {
15 | throw errors.unauthorized("invalidRefreshToken");
16 | }
17 |
18 | await refreshSession(ctx, sessionId);
19 | };
20 |
21 | export { handler };
22 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/auth/handlers/switch-workspace.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { ObjectId } from "mongodb";
3 | import { AuthenticatedContext } from "#lib/middleware";
4 | import { getUserSettingsCollection } from "#collections";
5 | import { errors } from "#lib/errors";
6 | import { switchWorkspaceSession } from "#lib/session";
7 |
8 | const inputSchema = z.object({
9 | workspaceId: z.string().describe("The ID of the workspace to switch to")
10 | });
11 | const handler = async (
12 | ctx: AuthenticatedContext,
13 | input: z.infer
14 | ): Promise => {
15 | const userSettingsCollection = getUserSettingsCollection(ctx.db);
16 | const { matchedCount } = await userSettingsCollection.updateOne(
17 | { userId: ctx.auth.userId },
18 | { $set: { currentWorkspaceId: new ObjectId(input.workspaceId) } }
19 | );
20 |
21 | if (!matchedCount) throw errors.notFound("userSettings");
22 |
23 | await switchWorkspaceSession(ctx);
24 | };
25 |
26 | export { inputSchema, handler };
27 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/auth/utils.ts:
--------------------------------------------------------------------------------
1 | import { ObjectId } from "mongodb";
2 | import * as OTPAuth from "otpauth";
3 | import { FullUser } from "#collections";
4 | import { errors } from "#lib/errors";
5 | import { UnderscoreID } from "#lib/mongo";
6 |
7 | const totpConfig = {
8 | issuer: "Vrite",
9 | algorithm: "SHA512",
10 | digits: 6,
11 | period: 30
12 | };
13 | const verifyTotp = (user: UnderscoreID>, totpToken?: string): void => {
14 | if (!totpToken) throw errors.unauthorized("totpTokenRequired");
15 |
16 | const totp = new OTPAuth.TOTP({
17 | ...totpConfig,
18 | label: user.username,
19 | secret: user.totpSecret
20 | });
21 | const result = totp.validate({ token: totpToken, window: 1 });
22 |
23 | if (typeof result !== "number") {
24 | throw errors.unauthorized("totpTokenInvalid");
25 | }
26 | };
27 |
28 | export { verifyTotp, totpConfig };
29 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/comments/handlers/get-thread.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { AuthenticatedContext } from "#lib/middleware";
3 | import { commentThread, getCommentThreadsCollection } from "#collections";
4 | import { errors } from "#lib/errors";
5 |
6 | const inputSchema = commentThread.pick({ fragment: true });
7 | const outputSchema = commentThread.omit({ comments: true });
8 | const handler = async (
9 | ctx: AuthenticatedContext,
10 | input: z.infer
11 | ): Promise> => {
12 | const commentThreadsCollection = getCommentThreadsCollection(ctx.db);
13 | const thread = await commentThreadsCollection.findOne({
14 | fragment: input.fragment,
15 | workspaceId: ctx.auth.workspaceId
16 | });
17 |
18 | if (!thread) throw errors.notFound("commentThread");
19 |
20 | return {
21 | ...thread,
22 | id: `${thread._id}`,
23 | date: thread.date.toISOString(),
24 | contentPieceId: `${thread.contentPieceId}`,
25 | variantId: thread.variantId ? `${thread.variantId}` : undefined
26 | };
27 | };
28 |
29 | export { inputSchema, outputSchema, handler };
30 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/content-groups/utils.ts:
--------------------------------------------------------------------------------
1 | import { ObjectId } from "mongodb";
2 | import { ContentGroup } from "#collections";
3 | import { FullContentGroup } from "#collections";
4 | import { UnderscoreID } from "#lib/mongo";
5 |
6 | const rearrangeContentGroups = (
7 | contentGroups: Array>>,
8 | ids: ObjectId[]
9 | ): ContentGroup[] => {
10 | return ids
11 | .map((id) => {
12 | const contentGroup = contentGroups.find((contentGroup) => {
13 | return contentGroup._id.equals(id);
14 | });
15 |
16 | if (!contentGroup) return null;
17 |
18 | return {
19 | id: `${contentGroup!._id}`,
20 | descendants: contentGroup.descendants.map((id) => `${id}`),
21 | ancestors: contentGroup.ancestors.map((id) => `${id}`),
22 | name: contentGroup.name
23 | };
24 | })
25 | .filter(Boolean) as ContentGroup[];
26 | };
27 |
28 | export { rearrangeContentGroups };
29 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/content-pieces/utils.ts:
--------------------------------------------------------------------------------
1 | import { Db, ObjectId } from "mongodb";
2 | import { getVariantsCollection } from "#collections";
3 | import { errors } from "#lib/errors";
4 |
5 | const getVariantDetails = async (
6 | db: Db,
7 | variantIdOrKey?: string
8 | ): Promise<{ variantId: ObjectId | null; variantKey: string | null }> => {
9 | const variantsCollection = getVariantsCollection(db);
10 |
11 | if (!variantIdOrKey) return { variantId: null, variantKey: null };
12 |
13 | const isId = ObjectId.isValid(variantIdOrKey);
14 | const variant = await variantsCollection.findOne({
15 | ...(isId && { _id: new ObjectId(variantIdOrKey) }),
16 | ...(!isId && { key: variantIdOrKey })
17 | });
18 |
19 | if (!variant) throw errors.notFound("variant");
20 |
21 | return { variantId: variant._id || null, variantKey: variant.key || null };
22 | };
23 |
24 | export { getVariantDetails };
25 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/extensions/handlers/configure.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { ObjectId } from "mongodb";
3 | import { AuthenticatedContext } from "#lib/middleware";
4 | import { extension, getExtensionsCollection } from "#collections";
5 | import { publishExtensionEvent } from "#events";
6 | import { errors } from "#lib/errors";
7 |
8 | const inputSchema = extension.pick({
9 | id: true,
10 | config: true
11 | });
12 | const handler = async (
13 | ctx: AuthenticatedContext,
14 | input: z.infer
15 | ): Promise => {
16 | const extensionsCollection = getExtensionsCollection(ctx.db);
17 | const { matchedCount } = await extensionsCollection.updateOne(
18 | { _id: new ObjectId(input.id) },
19 | {
20 | $set: {
21 | config: input.config
22 | }
23 | }
24 | );
25 |
26 | if (!matchedCount) {
27 | throw errors.notFound("extension");
28 | }
29 |
30 | publishExtensionEvent(ctx, `${ctx.auth.workspaceId}`, {
31 | action: "update",
32 | userId: `${ctx.auth.userId}`,
33 | data: {
34 | id: input.id,
35 | config: input.config
36 | }
37 | });
38 | };
39 |
40 | export { inputSchema, handler };
41 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/extensions/handlers/get.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { ObjectId } from "mongodb";
3 | import { extension, getExtensionsCollection } from "#collections";
4 | import { Context } from "#lib/context";
5 |
6 | const outputSchema = extension.partial();
7 | const handler = async (ctx: Context): Promise> => {
8 | const extensionsCollection = getExtensionsCollection(ctx.db);
9 | const extensionId = ctx.req.headers["x-vrite-extension-id"] as string | undefined;
10 |
11 | if (!extensionId) return {};
12 |
13 | const extension = await extensionsCollection.findOne({
14 | _id: new ObjectId(extensionId)
15 | });
16 |
17 | if (!extension) return {};
18 |
19 | return {
20 | ...extension,
21 | id: `${extension._id}`
22 | };
23 | };
24 |
25 | export { outputSchema, handler };
26 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/extensions/handlers/list.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { AuthenticatedContext } from "#lib/middleware";
3 | import { extension, getExtensionsCollection } from "#collections";
4 |
5 | const inputSchema = z
6 | .object({
7 | perPage: z.number().describe("Number of extensions per page").default(20),
8 | page: z.number().describe("Page number to fetch").default(1)
9 | })
10 | .default({});
11 | const outputSchema = z.array(extension);
12 | const handler = async (
13 | ctx: AuthenticatedContext,
14 | input: z.infer
15 | ): Promise> => {
16 | const extensionsCollection = getExtensionsCollection(ctx.db);
17 | const extensions = await extensionsCollection
18 | .find({
19 | workspaceId: ctx.auth.workspaceId
20 | })
21 | .sort({ _id: -1 })
22 | .skip((input.page - 1) * input.perPage)
23 | .limit(input.perPage)
24 | .toArray();
25 |
26 | return extensions.map(({ _id, workspaceId, ...extension }) => ({
27 | ...extension,
28 | id: `${_id}`
29 | }));
30 | };
31 |
32 | export { inputSchema, outputSchema, handler };
33 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/extensions/handlers/uninstall.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { ObjectId } from "mongodb";
3 | import { AuthenticatedContext } from "#lib/middleware";
4 | import { extension, getExtensionsCollection, getTokensCollection } from "#collections";
5 | import { publishExtensionEvent } from "#events";
6 | import { errors } from "#lib/errors";
7 |
8 | const inputSchema = extension.pick({ id: true });
9 | const handler = async (
10 | ctx: AuthenticatedContext,
11 | input: z.infer
12 | ): Promise => {
13 | const extensionsCollection = getExtensionsCollection(ctx.db);
14 | const tokensCollection = getTokensCollection(ctx.db);
15 | const { deletedCount } = await extensionsCollection.deleteOne({
16 | _id: new ObjectId(input.id)
17 | });
18 |
19 | if (!deletedCount) {
20 | throw errors.notFound("extension");
21 | }
22 |
23 | await tokensCollection.deleteOne({
24 | extensionId: new ObjectId(input.id)
25 | });
26 | publishExtensionEvent(ctx, `${ctx.auth.workspaceId}`, {
27 | action: "delete",
28 | userId: `${ctx.auth.userId}`,
29 | data: { id: input.id }
30 | });
31 | };
32 |
33 | export { inputSchema, handler };
34 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/git/handlers/reset-config.ts:
--------------------------------------------------------------------------------
1 | import { AuthenticatedContext } from "#lib/middleware";
2 | import { getGitDataCollection } from "#collections";
3 | import { errors } from "#lib/errors";
4 | import { publishGitDataEvent } from "#events";
5 |
6 | const handler = async (ctx: AuthenticatedContext): Promise => {
7 | const gitDataCollection = getGitDataCollection(ctx.db);
8 | const { deletedCount } = await gitDataCollection.deleteOne({
9 | workspaceId: ctx.auth.workspaceId
10 | });
11 |
12 | if (!deletedCount) throw errors.notFound("gitData");
13 |
14 | publishGitDataEvent(ctx, `${ctx.auth.workspaceId}`, { action: "reset", data: {} });
15 | };
16 |
17 | export { handler };
18 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/roles/handlers/create.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { ObjectId } from "mongodb";
3 | import { AuthenticatedContext } from "#lib/middleware";
4 | import { role, getRolesCollection } from "#collections";
5 | import { publishRoleEvent } from "#events";
6 |
7 | const inputSchema = role.omit({ id: true });
8 | const outputSchema = role.pick({ id: true });
9 | const handler = async (
10 | ctx: AuthenticatedContext,
11 | input: z.infer
12 | ): Promise> => {
13 | const rolesCollection = getRolesCollection(ctx.db);
14 | const role = {
15 | _id: new ObjectId(),
16 | workspaceId: ctx.auth.workspaceId,
17 | name: input.name,
18 | description: input.description,
19 | permissions: input.permissions
20 | };
21 |
22 | await rolesCollection.insertOne(role);
23 | publishRoleEvent(ctx, `${ctx.auth.workspaceId}`, {
24 | action: "create",
25 | data: {
26 | ...input,
27 | id: `${role._id}`
28 | }
29 | });
30 |
31 | return { id: `${role._id}` };
32 | };
33 |
34 | export { inputSchema, outputSchema, handler };
35 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/tags/handlers/create.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { ObjectId } from "mongodb";
3 | import { AuthenticatedContext } from "#lib/middleware";
4 | import { tag, getTagsCollection } from "#collections";
5 | import { publishTagEvent } from "#events";
6 |
7 | const inputSchema = tag.omit({ id: true });
8 | const outputSchema = tag.pick({ id: true });
9 | const handler = async (
10 | ctx: AuthenticatedContext,
11 | input: z.infer
12 | ): Promise> => {
13 | const tagsCollection = getTagsCollection(ctx.db);
14 | const tag = {
15 | ...input,
16 | _id: new ObjectId(),
17 | workspaceId: ctx.auth.workspaceId,
18 | value: (input.label || "").toLowerCase().replace(/\s/g, "_")
19 | };
20 |
21 | await tagsCollection.insertOne(tag);
22 | publishTagEvent(ctx, `${ctx.auth.workspaceId}`, {
23 | action: "create",
24 | data: {
25 | ...input,
26 | id: `${tag._id}`
27 | }
28 | });
29 |
30 | return { id: `${tag._id}` };
31 | };
32 |
33 | export { inputSchema, outputSchema, handler };
34 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/tags/handlers/get.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { ObjectId } from "mongodb";
3 | import { AuthenticatedContext } from "#lib/middleware";
4 | import { tag, getTagsCollection } from "#collections";
5 | import { errors } from "#lib/errors";
6 |
7 | const inputSchema = tag.pick({ id: true });
8 | const outputSchema = tag;
9 | const handler = async (
10 | ctx: AuthenticatedContext,
11 | input: z.infer
12 | ): Promise> => {
13 | const tagsCollection = getTagsCollection(ctx.db);
14 | const tag = await tagsCollection.findOne({
15 | workspaceId: ctx.auth.workspaceId,
16 | ...(input.id ? { _id: new ObjectId(input.id) } : {})
17 | });
18 |
19 | if (!tag) throw errors.notFound("tag");
20 |
21 | return {
22 | label: tag.label,
23 | color: tag.color,
24 | id: `${tag._id}`
25 | };
26 | };
27 |
28 | export { inputSchema, outputSchema, handler };
29 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/tags/handlers/list.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { AuthenticatedContext } from "#lib/middleware";
3 | import { getTagsCollection, tag } from "#collections";
4 |
5 | const inputSchema = z.object({
6 | perPage: z.number().describe("The number of tags to return per page").default(20),
7 | page: z.number().describe("The page number to fetch").default(1)
8 | });
9 | const outputSchema = z.array(tag);
10 | const handler = async (
11 | ctx: AuthenticatedContext,
12 | input: z.infer
13 | ): Promise> => {
14 | const tagsCollection = getTagsCollection(ctx.db);
15 | const tags = await tagsCollection
16 | .find({
17 | workspaceId: ctx.auth.workspaceId
18 | })
19 | .sort("_id", -1)
20 | .skip((input.page - 1) * input.perPage)
21 | .limit(input.perPage)
22 | .toArray();
23 |
24 | return tags.map((tag) => {
25 | return { ...tag, id: `${tag._id}` };
26 | });
27 | };
28 |
29 | export { inputSchema, outputSchema, handler };
30 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/tags/handlers/search.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { AuthenticatedContext } from "#lib/middleware";
3 | import { tag, getTagsCollection } from "#collections";
4 | import { stringToRegex } from "#lib/utils";
5 |
6 | const inputSchema = z.object({
7 | query: z.string().describe("Query to search tag values by").optional()
8 | });
9 | const outputSchema = z.array(tag);
10 | const handler = async (
11 | ctx: AuthenticatedContext,
12 | input: z.infer
13 | ): Promise> => {
14 | const tagsCollection = getTagsCollection(ctx.db);
15 | const tags = await tagsCollection
16 | .find({
17 | workspaceId: ctx.auth.workspaceId,
18 | ...(input.query ? { value: stringToRegex(input.query) } : {})
19 | })
20 | .limit(10)
21 | .sort("_id", -1)
22 | .toArray();
23 |
24 | return tags.map((tag) => {
25 | return { ...tag, id: `${tag._id}` };
26 | });
27 | };
28 |
29 | export { inputSchema, outputSchema, handler };
30 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/tokens/handlers/create.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { AuthenticatedContext } from "#lib/middleware";
3 | import { token } from "#collections";
4 | import { publishTokenEvent } from "#events";
5 | import { createToken } from "#lib/utils";
6 |
7 | const inputSchema = token.omit({ id: true });
8 | const outputSchema = token.pick({ id: true }).extend({
9 | value: z.string().describe("Value of the token")
10 | });
11 | const handler = async (
12 | ctx: AuthenticatedContext,
13 | input: z.infer
14 | ): Promise> => {
15 | const { token, value } = await createToken(input, ctx);
16 |
17 | publishTokenEvent(ctx, `${ctx.auth.workspaceId}`, {
18 | action: "create",
19 | data: {
20 | id: `${token._id}`,
21 | name: token.name || "",
22 | description: token.description || "",
23 | permissions: token.permissions
24 | }
25 | });
26 |
27 | return {
28 | value,
29 | id: `${token._id}`
30 | };
31 | };
32 |
33 | export { inputSchema, outputSchema, handler };
34 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/tokens/handlers/delete.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { ObjectId } from "mongodb";
3 | import { AuthenticatedContext } from "#lib/middleware";
4 | import { getTokensCollection, token } from "#collections";
5 | import { publishTokenEvent } from "#events";
6 | import { errors } from "#lib/errors";
7 |
8 | const inputSchema = token.pick({ id: true });
9 | const handler = async (
10 | ctx: AuthenticatedContext,
11 | input: z.infer
12 | ): Promise => {
13 | const tokensCollection = getTokensCollection(ctx.db);
14 | const { deletedCount } = await tokensCollection.deleteOne({ _id: new ObjectId(input.id) });
15 |
16 | if (deletedCount === 0) throw errors.notFound("token");
17 |
18 | publishTokenEvent(ctx, `${ctx.auth.workspaceId}`, {
19 | action: "delete",
20 | data: {
21 | id: input.id
22 | }
23 | });
24 | };
25 |
26 | export { inputSchema, handler };
27 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/tokens/handlers/get.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { ObjectId } from "mongodb";
3 | import { AuthenticatedContext } from "#lib/middleware";
4 | import { getTokensCollection, token } from "#collections";
5 | import { errors } from "#lib/errors";
6 |
7 | const inputSchema = token.pick({ id: true });
8 | const outputSchema = token;
9 | const handler = async (
10 | ctx: AuthenticatedContext,
11 | input: z.infer
12 | ): Promise> => {
13 | const tokensCollection = getTokensCollection(ctx.db);
14 | const token = await tokensCollection.findOne({
15 | _id: new ObjectId(input.id),
16 | workspaceId: ctx.auth.workspaceId
17 | });
18 |
19 | if (!token) throw errors.notFound("token");
20 |
21 | return {
22 | id: `${token._id}`,
23 | name: token.name || "",
24 | description: token.description || "",
25 | permissions: token.permissions
26 | };
27 | };
28 |
29 | export { inputSchema, outputSchema, handler };
30 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/tokens/handlers/update.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { ObjectId } from "mongodb";
3 | import { AuthenticatedContext } from "#lib/middleware";
4 | import { getTokensCollection, token } from "#collections";
5 | import { errors } from "#lib/errors";
6 | import { publishTokenEvent } from "#events";
7 |
8 | const inputSchema = token.partial().required({ id: true });
9 | const handler = async (
10 | ctx: AuthenticatedContext,
11 | input: z.infer
12 | ): Promise => {
13 | const tokensCollection = getTokensCollection(ctx.db);
14 | const { id, ...update } = input;
15 | const { matchedCount } = await tokensCollection.updateOne(
16 | {
17 | _id: new ObjectId(id),
18 | workspaceId: ctx.auth.workspaceId
19 | },
20 | {
21 | $set: update
22 | }
23 | );
24 |
25 | if (matchedCount === 0) throw errors.notFound("token");
26 |
27 | publishTokenEvent(ctx, `${ctx.auth.workspaceId}`, {
28 | action: "update",
29 | data: {
30 | id,
31 | ...update
32 | }
33 | });
34 | };
35 |
36 | export { inputSchema, handler };
37 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/user-settings/handlers/get-workspace-id.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { AuthenticatedContext } from "#lib/middleware";
3 | import { getWorkspacesCollection, getWorkspaceMembershipsCollection } from "#collections";
4 | import { errors } from "#lib/errors";
5 |
6 | const outputSchema = z.object({
7 | workspaceId: z.string().describe("ID of the workspace the user is currently signed-into")
8 | });
9 | const handler = async (ctx: AuthenticatedContext): Promise> => {
10 | const workspacesCollection = getWorkspacesCollection(ctx.db);
11 | const workspaceMembershipsCollection = getWorkspaceMembershipsCollection(ctx.db);
12 | const workspace = await workspacesCollection.findOne({
13 | _id: ctx.auth.workspaceId
14 | });
15 |
16 | if (!workspace) throw errors.notFound("workspace");
17 |
18 | const workspaceMembership = await workspaceMembershipsCollection.findOne({
19 | workspaceId: ctx.auth.workspaceId,
20 | userId: ctx.auth.userId
21 | });
22 |
23 | if (!workspaceMembership) throw errors.notFound("workspaceMembership");
24 |
25 | return { workspaceId: `${ctx.auth.workspaceId}` };
26 | };
27 |
28 | export { outputSchema, handler };
29 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/user-settings/handlers/get.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { AuthenticatedContext } from "#lib/middleware";
3 | import { appearanceSettings, getUserSettingsCollection } from "#collections";
4 | import { errors } from "#lib/errors";
5 |
6 | const outputSchema = appearanceSettings;
7 | const handler = async (ctx: AuthenticatedContext): Promise> => {
8 | const userSettingsCollection = getUserSettingsCollection(ctx.db);
9 | const userSettings = await userSettingsCollection.findOne({
10 | userId: ctx.auth.userId
11 | });
12 |
13 | if (!userSettings) throw errors.notFound("userSettings");
14 |
15 | return {
16 | codeEditorTheme: userSettings.uiTheme,
17 | uiTheme: userSettings.uiTheme,
18 | accentColor: userSettings.accentColor
19 | };
20 | };
21 |
22 | export { outputSchema, handler };
23 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/user-settings/handlers/update.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { AuthenticatedContext } from "#lib/middleware";
3 | import { appearanceSettings, getUserSettingsCollection } from "#collections";
4 | import { publishUserSettingsEvent } from "#events";
5 | import { errors } from "#lib/errors";
6 |
7 | const inputSchema = appearanceSettings.omit({ codeEditorTheme: true }).partial();
8 | const handler = async (
9 | ctx: AuthenticatedContext,
10 | input: z.infer
11 | ): Promise => {
12 | const userSettingsCollection = getUserSettingsCollection(ctx.db);
13 | const { matchedCount } = await userSettingsCollection.updateOne(
14 | { userId: ctx.auth.userId },
15 | { $set: { ...input } }
16 | );
17 |
18 | if (!matchedCount) throw errors.notFound("userSettings");
19 |
20 | publishUserSettingsEvent(ctx, `${ctx.auth.userId}`, {
21 | action: "update",
22 | data: { ...input, ...(input.uiTheme ? { codeEditorTheme: input.uiTheme } : {}) }
23 | });
24 | };
25 |
26 | export { inputSchema, handler };
27 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/utils/handlers/host-config.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { hostConfig } from "#lib/host-config";
3 | import { Context } from "#lib/context";
4 |
5 | const outputSchema = hostConfig;
6 | const handler = async (ctx: Context): Promise> => {
7 | return ctx.fastify.hostConfig;
8 | };
9 |
10 | export { outputSchema, handler };
11 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/variants/handlers/create.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { ObjectId } from "mongodb";
3 | import { AuthenticatedContext } from "#lib/middleware";
4 | import { variant, getVariantsCollection } from "#collections";
5 | import { publishVariantEvent } from "#events";
6 |
7 | const inputSchema = variant.omit({ id: true });
8 | const outputSchema = variant.pick({ id: true });
9 | const handler = async (
10 | ctx: AuthenticatedContext,
11 | input: z.infer
12 | ): Promise> => {
13 | const variantsCollection = getVariantsCollection(ctx.db);
14 | const variant = {
15 | _id: new ObjectId(),
16 | workspaceId: ctx.auth.workspaceId,
17 | ...input
18 | };
19 |
20 | await variantsCollection.insertOne(variant);
21 | publishVariantEvent(ctx, `${ctx.auth.workspaceId}`, {
22 | action: "create",
23 | data: { ...input, id: `${variant._id}` }
24 | });
25 |
26 | return { id: `${variant._id}` };
27 | };
28 |
29 | export { inputSchema, outputSchema, handler };
30 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/variants/handlers/list.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { AuthenticatedContext } from "#lib/middleware";
3 | import { getVariantsCollection, variant } from "#collections";
4 |
5 | const outputSchema = z.array(variant);
6 | const handler = async (ctx: AuthenticatedContext): Promise> => {
7 | const variantsCollection = getVariantsCollection(ctx.db);
8 | const variants = await variantsCollection
9 | .find({
10 | workspaceId: ctx.auth.workspaceId
11 | })
12 | .sort("_id", -1)
13 | .toArray();
14 |
15 | return variants.map(({ _id, label, key, description }) => {
16 | return {
17 | id: `${_id}`,
18 | label,
19 | key,
20 | description
21 | };
22 | });
23 | };
24 |
25 | export { outputSchema, handler };
26 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/variants/handlers/update.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { ObjectId } from "mongodb";
3 | import { AuthenticatedContext } from "#lib/middleware";
4 | import { variant, getVariantsCollection } from "#collections";
5 | import { publishVariantEvent } from "#events";
6 | import { errors } from "#lib/errors";
7 |
8 | const inputSchema = variant.partial().required({ id: true });
9 | const handler = async (
10 | ctx: AuthenticatedContext,
11 | input: z.infer
12 | ): Promise => {
13 | const variantsCollection = getVariantsCollection(ctx.db);
14 | const { id, ...update } = input;
15 | const { matchedCount } = await variantsCollection.updateOne(
16 | { _id: new ObjectId(id), workspaceId: ctx.auth.workspaceId },
17 | { $set: update }
18 | );
19 |
20 | if (!matchedCount) throw errors.notFound("variant");
21 |
22 | publishVariantEvent(ctx, `${ctx.auth.workspaceId}`, { action: "update", data: input });
23 | };
24 |
25 | export { inputSchema, handler };
26 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/webhooks/handlers/delete.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { ObjectId } from "mongodb";
3 | import { AuthenticatedContext } from "#lib/middleware";
4 | import { getWebhooksCollection, webhook } from "#collections";
5 | import { publishWebhookEvent } from "#events";
6 | import { errors } from "#lib/errors";
7 |
8 | const inputSchema = webhook.pick({ id: true });
9 | const handler = async (
10 | ctx: AuthenticatedContext,
11 | input: z.infer
12 | ): Promise => {
13 | const webhooksCollection = getWebhooksCollection(ctx.db);
14 | const { deletedCount } = await webhooksCollection.deleteOne({
15 | _id: new ObjectId(input.id),
16 | workspaceId: ctx.auth.workspaceId
17 | });
18 |
19 | if (!deletedCount) throw errors.notFound("webhook");
20 |
21 | publishWebhookEvent(ctx, `${ctx.auth.workspaceId}`, {
22 | action: "delete",
23 | data: input
24 | });
25 | };
26 |
27 | export { inputSchema, handler };
28 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/webhooks/handlers/reveal-secret.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { ObjectId } from "mongodb";
3 | import { AuthenticatedContext } from "#lib/middleware";
4 | import { webhook, getWebhooksCollection } from "#collections";
5 | import { errors } from "#lib/errors";
6 |
7 | const inputSchema = webhook.pick({ id: true });
8 | const outputSchema = webhook.pick({ secret: true });
9 | const handler = async (
10 | ctx: AuthenticatedContext,
11 | input: z.infer
12 | ): Promise> => {
13 | const webhooksCollection = getWebhooksCollection(ctx.db);
14 | const webhook = await webhooksCollection.findOne({
15 | _id: new ObjectId(input.id),
16 | workspaceId: ctx.auth.workspaceId
17 | });
18 |
19 | if (!webhook) throw errors.notFound("webhook");
20 |
21 | return {
22 | secret: webhook.secret
23 | };
24 | };
25 |
26 | export { inputSchema, outputSchema, handler };
27 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/workspace-memberships/handlers/delete.ts:
--------------------------------------------------------------------------------
1 | import { removeMemberFromWorkspace } from "../utils";
2 | import { z } from "zod";
3 | import { ObjectId } from "mongodb";
4 | import { AuthenticatedContext } from "#lib/middleware";
5 | import { UnderscoreID, zodId } from "#lib/mongo";
6 | import { FullWorkspaceMembership, workspaceMembership } from "#collections";
7 |
8 | declare module "fastify" {
9 | interface RouteCallbacks {
10 | "workspaceMemberships.delete": {
11 | ctx: AuthenticatedContext;
12 | data: {
13 | workspaceMembership: UnderscoreID>;
14 | };
15 | };
16 | }
17 | }
18 |
19 | const inputSchema = workspaceMembership.pick({ id: true });
20 | const handler = async (
21 | ctx: AuthenticatedContext,
22 | input: z.infer
23 | ): Promise => {
24 | const affectedWorkspaceMembership = await removeMemberFromWorkspace(ctx, input.id);
25 |
26 | ctx.fastify.routeCallbacks.run("workspaceMemberships.leave", ctx, {
27 | workspaceMembership: affectedWorkspaceMembership
28 | });
29 | };
30 |
31 | export { inputSchema, handler };
32 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/workspace-memberships/handlers/leave.ts:
--------------------------------------------------------------------------------
1 | import { removeMemberFromWorkspace } from "../utils";
2 | import { ObjectId } from "mongodb";
3 | import { AuthenticatedContext } from "#lib/middleware";
4 | import { UnderscoreID } from "#lib/mongo";
5 | import { FullWorkspaceMembership } from "#collections";
6 |
7 | declare module "fastify" {
8 | interface RouteCallbacks {
9 | "workspaceMemberships.leave": {
10 | ctx: AuthenticatedContext;
11 | data: {
12 | workspaceMembership: UnderscoreID>;
13 | };
14 | };
15 | }
16 | }
17 |
18 | const handler = async (ctx: AuthenticatedContext): Promise => {
19 | const affectedWorkspaceMembership = await removeMemberFromWorkspace(ctx);
20 |
21 | ctx.fastify.routeCallbacks.run("workspaceMemberships.leave", ctx, {
22 | workspaceMembership: affectedWorkspaceMembership
23 | });
24 | };
25 |
26 | export { handler };
27 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/workspace-settings/handlers/get.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { AuthenticatedContext } from "#lib/middleware";
3 | import { getWorkspaceSettingsCollection, workspaceSettings } from "#collections";
4 | import { errors } from "#lib/errors";
5 |
6 | const outputSchema = workspaceSettings;
7 | const handler = async (ctx: AuthenticatedContext): Promise> => {
8 | const workspaceSettingsCollection = getWorkspaceSettingsCollection(ctx.db);
9 | const workspaceSettings = await workspaceSettingsCollection.findOne({
10 | workspaceId: ctx.auth.workspaceId
11 | });
12 |
13 | if (!workspaceSettings) {
14 | throw errors.notFound("workspaceSettings");
15 | }
16 |
17 | return {
18 | id: `${workspaceSettings._id}`,
19 | prettierConfig: workspaceSettings.prettierConfig,
20 | metadata: workspaceSettings.metadata,
21 | marks: workspaceSettings.marks,
22 | blocks: workspaceSettings.blocks,
23 | embeds: workspaceSettings.embeds,
24 | dashboardViews: workspaceSettings.dashboardViews
25 | };
26 | };
27 |
28 | export { outputSchema, handler };
29 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/workspace-settings/handlers/prettier-schema.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import zodToJsonSchema from "zod-to-json-schema";
3 | import { prettierConfig } from "#collections";
4 | import { Context } from "#lib/context";
5 |
6 | const outputSchema = z.any();
7 | const handler = async (_ctx: Context): Promise> => {
8 | return zodToJsonSchema(prettierConfig);
9 | };
10 |
11 | export { outputSchema, handler };
12 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/workspaces/handlers/create.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { AuthenticatedContext } from "#lib/middleware";
3 | import { getUsersCollection, workspace } from "#collections";
4 | import { createWorkspace } from "#lib/workspace";
5 | import { errors } from "#lib/errors";
6 |
7 | const inputSchema = workspace.pick({ description: true, logo: true, name: true });
8 | const outputSchema = workspace.pick({ id: true });
9 | const handler = async (
10 | ctx: AuthenticatedContext,
11 | input: z.infer
12 | ): Promise> => {
13 | const usersCollection = getUsersCollection(ctx.db);
14 | const user = await usersCollection.findOne({
15 | _id: ctx.auth.userId
16 | });
17 |
18 | if (!user) throw errors.notFound("user");
19 |
20 | const { workspaceId } = await createWorkspace(user, ctx.fastify, input);
21 |
22 | return { id: `${workspaceId}` };
23 | };
24 |
25 | export { inputSchema, outputSchema, handler };
26 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/workspaces/handlers/delete.ts:
--------------------------------------------------------------------------------
1 | import { outputSchema } from "./get";
2 | import { inputSchema } from "./update";
3 | import { AuthenticatedContext } from "#lib/middleware";
4 | import { publishWorkspaceEvent } from "#events";
5 | import { deleteWorkspace } from "#lib/workspace";
6 |
7 | const handler = async (ctx: AuthenticatedContext): Promise => {
8 | await deleteWorkspace(ctx.auth.workspaceId, ctx.fastify);
9 | publishWorkspaceEvent(ctx, `${ctx.auth.workspaceId}`, {
10 | action: "delete",
11 | data: {
12 | id: `${ctx.auth.workspaceId}`
13 | }
14 | });
15 | };
16 |
17 | export { inputSchema, outputSchema, handler };
18 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/workspaces/handlers/get.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { AuthenticatedContext } from "#lib/middleware";
3 | import { workspace, getWorkspacesCollection } from "#collections";
4 | import { errors } from "#lib/errors";
5 |
6 | const outputSchema = workspace.pick({
7 | id: true,
8 | name: true,
9 | logo: true,
10 | description: true
11 | });
12 | const handler = async (ctx: AuthenticatedContext): Promise> => {
13 | const workspacesCollection = getWorkspacesCollection(ctx.db);
14 | const workspace = await workspacesCollection.findOne({
15 | _id: ctx.auth.workspaceId
16 | });
17 |
18 | if (!workspace) throw errors.notFound("workspace");
19 |
20 | return {
21 | id: `${workspace._id}`,
22 | name: workspace.name,
23 | logo: workspace.logo,
24 | description: workspace.description
25 | };
26 | };
27 |
28 | export { outputSchema, handler };
29 |
--------------------------------------------------------------------------------
/packages/backend/src/routes/workspaces/handlers/update.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { AuthenticatedContext } from "#lib/middleware";
3 | import { workspace, getWorkspacesCollection } from "#collections";
4 | import { publishWorkspaceEvent } from "#events";
5 | import { errors } from "#lib/errors";
6 |
7 | const inputSchema = workspace
8 | .pick({ id: true, description: true, logo: true, name: true })
9 | .partial()
10 | .required({ id: true });
11 | const handler = async (
12 | ctx: AuthenticatedContext,
13 | input: z.infer
14 | ): Promise => {
15 | const workspacesCollection = getWorkspacesCollection(ctx.db);
16 | const { matchedCount } = await workspacesCollection.updateOne(
17 | { _id: ctx.auth.workspaceId },
18 | {
19 | $set: input
20 | }
21 | );
22 |
23 | if (!matchedCount) throw errors.notFound("workspace");
24 |
25 | publishWorkspaceEvent(ctx, `${ctx.auth.workspaceId}`, {
26 | action: "update",
27 | data: input
28 | });
29 | };
30 |
31 | export { inputSchema, handler };
32 |
--------------------------------------------------------------------------------
/packages/backend/src/server.ts:
--------------------------------------------------------------------------------
1 | import { envSchema } from "./env";
2 | import createFastify, { FastifyInstance } from "fastify";
3 | import envPlugin from "@fastify/env";
4 | import zodToJsonSchema from "zod-to-json-schema";
5 | import { hostConfigPlugin } from "#plugins";
6 | import { routeCallbacksPlugin } from "#plugins/route-callbacks";
7 |
8 | const createServer = async (
9 | configure?: (server: FastifyInstance) => Promise
10 | ): Promise => {
11 | const server = createFastify({
12 | maxParamLength: 5000,
13 | logger: false
14 | });
15 |
16 | await server
17 | .register(envPlugin, {
18 | dotenv: true,
19 | schema: zodToJsonSchema(envSchema)
20 | })
21 | .register(hostConfigPlugin)
22 | .register(routeCallbacksPlugin);
23 |
24 | if (configure) {
25 | await configure(server);
26 | }
27 |
28 | return server;
29 | };
30 |
31 | export { createServer };
32 |
--------------------------------------------------------------------------------
/packages/backend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "esModuleInterop": true,
6 | "moduleResolution": "node",
7 | "isolatedModules": true,
8 | "strict": true,
9 | "outDir": "build",
10 | "skipLibCheck": true,
11 | "resolveJsonModule": true,
12 | "paths": {
13 | "#*": ["./src/*"]
14 | }
15 | },
16 | "include": ["src/**/*.ts"]
17 | }
18 |
--------------------------------------------------------------------------------
/packages/cli/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Vrite, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/cli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vrite/cli",
3 | "version": "0.0.7",
4 | "private": false,
5 | "description": "CLI for Vrite - open-source developer content platform",
6 | "license": "MIT",
7 | "type": "module",
8 | "scripts": {
9 | "publish": "pnpm publish --access public --no-git-checks"
10 | },
11 | "bugs": {
12 | "url": "https://github.com/vriteio/vrite/issues"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/vriteio/vrite.git",
17 | "directory": "packages/cli"
18 | },
19 | "keywords": [
20 | "vrite",
21 | "headless cms",
22 | "cms",
23 | "typescript",
24 | "javascript",
25 | "cli"
26 | ],
27 | "dependencies": {
28 | "chalk": "^5.3.0",
29 | "commander": "^12.0.0",
30 | "esbuild": "^0.20.0"
31 | },
32 | "bin": {
33 | "vrite": "./index.mjs"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/components/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vrite/components",
3 | "private": true,
4 | "main": "./src/index.ts",
5 | "types": "./src/index.ts",
6 | "dependencies": {
7 | "@floating-ui/dom": "^1.6.1",
8 | "@mdi/js": "^7.4.47",
9 | "@solid-primitives/active-element": "^2.0.18",
10 | "@solid-primitives/media": "^2.2.6",
11 | "clsx": "^2.1.0",
12 | "solid-js": "^1.8.14"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/components/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./primitives";
2 |
--------------------------------------------------------------------------------
/packages/components/src/primitives/card.tsx:
--------------------------------------------------------------------------------
1 | import { Ref } from "../ref";
2 | import clsx from "clsx";
3 | import { Component, JSX, mergeProps, splitProps } from "solid-js";
4 |
5 | const cardColors = {
6 | base: `:base: border-gray-200 bg-gray-50 dark:bg-gray-900 dark:border-gray-700`,
7 | contrast: `:base: bg-gray-100 border-gray-200 dark:bg-gray-800 dark:border-gray-700`,
8 | soft: `:base: bg-gray-200 border-gray-300 dark:bg-gray-700 dark:bg-opacity-30 dark:border-gray-700`,
9 | primary: `:base: text-white bg-gradient-to-tr border-none`
10 | };
11 |
12 | interface CardProps extends JSX.HTMLAttributes {
13 | color?: keyof typeof cardColors;
14 | ref?: Ref[1];
15 | }
16 |
17 | const Card: Component = (providedProps) => {
18 | const props = mergeProps({ color: "base" } as Required, providedProps);
19 | const [, passedProps] = splitProps(props, ["class", "color", "children", "ref"]);
20 |
21 | return (
22 |
27 | {props.children}
28 |
29 | );
30 | };
31 |
32 | export { Card };
33 |
--------------------------------------------------------------------------------
/packages/components/src/primitives/checkbox.tsx:
--------------------------------------------------------------------------------
1 | import clsx from "clsx";
2 | import { Component, JSX, splitProps } from "solid-js";
3 |
4 | interface CheckboxProps extends JSX.InputHTMLAttributes {
5 | checked: boolean;
6 | class?: string;
7 | setChecked(value: boolean): void;
8 | }
9 |
10 | const Checkbox: Component = (props) => {
11 | const [, passedProps] = splitProps(props, ["checked", "setChecked", "class"]);
12 |
13 | return (
14 | props.setChecked(event.currentTarget.checked)}
19 | {...passedProps}
20 | />
21 | );
22 | };
23 |
24 | export { Checkbox };
25 |
--------------------------------------------------------------------------------
/packages/components/src/primitives/fragment.tsx:
--------------------------------------------------------------------------------
1 | import { ParentComponent } from "solid-js";
2 |
3 | const Fragment: ParentComponent> = (props) => props.children;
4 |
5 | export { Fragment };
6 |
--------------------------------------------------------------------------------
/packages/components/src/primitives/heading.tsx:
--------------------------------------------------------------------------------
1 | import clsx from "clsx";
2 | import { Component, JSX } from "solid-js";
3 | import { Dynamic } from "solid-js/web";
4 |
5 | type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
6 | interface HeadingProps {
7 | children: JSX.Element;
8 | class?: string;
9 | level?: HeadingLevel;
10 | }
11 |
12 | const levelClassNames = {
13 | 1: "text-2xl",
14 | 2: "text-xl",
15 | 3: "text-lg",
16 | 4: "text-md",
17 | 5: "text-sm",
18 | 6: "text-xs"
19 | };
20 | const Heading: Component = (props) => {
21 | const tag = (): string => `h${props.level || 1}`;
22 |
23 | return (
24 |
28 | {props.children}
29 |
30 | );
31 | };
32 |
33 | export { Heading };
34 |
--------------------------------------------------------------------------------
/packages/components/src/primitives/icon.tsx:
--------------------------------------------------------------------------------
1 | import clsx from "clsx";
2 | import { Component, JSX, splitProps } from "solid-js";
3 |
4 | interface IconProps extends JSX.SvgSVGAttributes {
5 | path: string;
6 | class?: string;
7 | }
8 |
9 | const Icon: Component = (props) => {
10 | const [, passedProps] = splitProps(props, ["class", "path"]);
11 |
12 | return (
13 |
22 | );
23 | };
24 |
25 | export { Icon };
26 |
--------------------------------------------------------------------------------
/packages/components/src/primitives/index.tsx:
--------------------------------------------------------------------------------
1 | export * from "./button";
2 | export * from "./card";
3 | export * from "./checkbox";
4 | export * from "./dropdown";
5 | export * from "./fragment";
6 | export * from "./heading";
7 | export * from "./icon";
8 | export * from "./input";
9 | export * from "./loader";
10 | export * from "./overlay";
11 | export * from "./select";
12 | export * from "./tooltip";
13 |
--------------------------------------------------------------------------------
/packages/components/src/primitives/loader.tsx:
--------------------------------------------------------------------------------
1 | import clsx from "clsx";
2 | import { Component } from "solid-js";
3 |
4 | const sizes = {
5 | small: `:base: w-4 h-4`,
6 | medium: `:base: w-6 h-6`,
7 | large: `:base: w-8 h-8`
8 | };
9 |
10 | interface LoaderProps {
11 | class?: string;
12 | size?: keyof typeof sizes;
13 | color?: "base" | "primary";
14 | }
15 |
16 | const Loader: Component = (props) => {
17 | return (
18 |
31 | );
32 | };
33 |
34 | export { Loader };
35 |
--------------------------------------------------------------------------------
/packages/components/src/ref.ts:
--------------------------------------------------------------------------------
1 | type Ref = [() => V, (value: V) => void];
2 |
3 | const createRef = (initialValue: V): Ref => {
4 | let ref = initialValue;
5 |
6 | return [
7 | () => ref,
8 | (value) => {
9 | ref = value;
10 | }
11 | ];
12 | };
13 |
14 | export { createRef };
15 | export type { Ref };
16 |
--------------------------------------------------------------------------------
/packages/components/src/utils.ts:
--------------------------------------------------------------------------------
1 | const isTouchDevice = (): boolean => {
2 | return (
3 | "ontouchstart" in window ||
4 | navigator.maxTouchPoints > 0 ||
5 | ("msMaxTouchPoints" in navigator &&
6 | typeof navigator.msMaxTouchPoints === "number" &&
7 | navigator.msMaxTouchPoints > 0)
8 | );
9 | };
10 |
11 | export { isTouchDevice };
12 |
--------------------------------------------------------------------------------
/packages/components/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "target": "ESNext",
5 | "module": "ESNext",
6 | "moduleResolution": "node",
7 | "allowSyntheticDefaultImports": true,
8 | "esModuleInterop": true,
9 | "resolveJsonModule": true,
10 | "jsx": "preserve",
11 | "jsxImportSource": "solid-js",
12 | "baseUrl": ".",
13 | "paths": {
14 | "#*": ["./src/*"]
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/editor/src/document.ts:
--------------------------------------------------------------------------------
1 | import { Document as BaseDocument } from "@tiptap/extension-document";
2 |
3 | const Document = BaseDocument.extend({
4 | content: "block+"
5 | });
6 |
7 | export { Document };
8 |
--------------------------------------------------------------------------------
/packages/editor/src/horizontal-rule.ts:
--------------------------------------------------------------------------------
1 | import { nodeInputRule } from "./node-input-rule";
2 | import { nodePasteRule } from "./node-paste-rule";
3 | import { HorizontalRule as BaseHorizontalRule } from "@tiptap/extension-horizontal-rule";
4 |
5 | const HorizontalRule = BaseHorizontalRule.extend({
6 | addInputRules() {
7 | return [
8 | nodeInputRule({
9 | find: /^(?:---|—-|___\s|\*\*\*\s)$/,
10 | type: this.type
11 | })
12 | ];
13 | },
14 | addPasteRules() {
15 | return [
16 | nodePasteRule({
17 | find: /^(?:---|—-|___|\*\*\*)\s*$/g,
18 | type: this.type
19 | })
20 | ];
21 | }
22 | });
23 |
24 | export { HorizontalRule };
25 |
--------------------------------------------------------------------------------
/packages/editor/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./code-block";
2 | export * from "./element";
3 | export * from "./task-item";
4 | export * from "./list-item";
5 | export * from "./embed";
6 | export * from "./image";
7 | export * from "./document";
8 | export * from "./heading";
9 | export * from "./table";
10 | export * from "./table-cell";
11 | export * from "./table-header";
12 | export * from "./marks";
13 | export * from "./comment";
14 | export * from "./horizontal-rule";
15 | export * from "./node-input-rule";
16 | export * from "./node-paste-rule";
17 | export * from "./wrapping-input-rule";
18 | export * from "./unique-id";
19 | export { HardBreak } from "@tiptap/extension-hard-break";
20 | export { Text } from "@tiptap/extension-text";
21 | export { Blockquote } from "@tiptap/extension-blockquote";
22 | export { BulletList } from "@tiptap/extension-bullet-list";
23 | export { OrderedList } from "@tiptap/extension-ordered-list";
24 | export { TaskList } from "@tiptap/extension-task-list";
25 | export { TableRow } from "@tiptap/extension-table-row";
26 | export { Paragraph } from "@tiptap/extension-paragraph";
27 | export { History } from "@tiptap/extension-history";
28 |
--------------------------------------------------------------------------------
/packages/editor/src/list-item.ts:
--------------------------------------------------------------------------------
1 | import { ListItem as BaseListItem } from "@tiptap/extension-list-item";
2 |
3 | const ListItem = BaseListItem.extend({
4 | content: "paragraph block*"
5 | });
6 |
7 | export { ListItem };
8 |
--------------------------------------------------------------------------------
/packages/editor/src/table-cell.ts:
--------------------------------------------------------------------------------
1 | import { mergeAttributes } from "@tiptap/core";
2 | import { TableCell as BaseTableCell } from "@tiptap/extension-table-cell";
3 |
4 | const TableCell = BaseTableCell.extend({
5 | content: "block+",
6 | renderHTML({ HTMLAttributes }) {
7 | return ["td", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
8 | }
9 | });
10 |
11 | export { TableCell };
12 |
--------------------------------------------------------------------------------
/packages/editor/src/table-header.ts:
--------------------------------------------------------------------------------
1 | import { mergeAttributes } from "@tiptap/core";
2 | import { TableHeader as BaseTableHeader } from "@tiptap/extension-table-header";
3 |
4 | const TableHeader = BaseTableHeader.extend({
5 | content: "block+",
6 | renderHTML({ HTMLAttributes }) {
7 | return ["th", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
8 | }
9 | });
10 |
11 | export { TableHeader };
12 |
--------------------------------------------------------------------------------
/packages/editor/src/task-item.ts:
--------------------------------------------------------------------------------
1 | import { TaskItem as BaseTaskItem } from "@tiptap/extension-task-item";
2 |
3 | const TaskItem = BaseTaskItem.extend({
4 | content: "paragraph block*",
5 | addKeyboardShortcuts() {
6 | return {
7 | ...this.parent?.(),
8 | Tab: () => this.editor.commands.sinkListItem(this.name)
9 | };
10 | }
11 | });
12 |
13 | export { TaskItem };
14 |
--------------------------------------------------------------------------------
/packages/editor/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "target": "ESNext",
5 | "module": "ESNext",
6 | "moduleResolution": "node",
7 | "allowSyntheticDefaultImports": true,
8 | "esModuleInterop": true,
9 | "resolveJsonModule": true,
10 | "jsx": "preserve",
11 | "jsxImportSource": "solid-js",
12 | "baseUrl": ".",
13 | "paths": {
14 | "#*": ["./src/*"]
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/emails/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vrite/emails",
3 | "private": true,
4 | "main": "./src/index.ts",
5 | "types": "./src/index.ts",
6 | "workspaces": [
7 | ".react-email"
8 | ],
9 | "scripts": {
10 | "preview": "email dev"
11 | },
12 | "dependencies": {
13 | "@react-email/components": "0.0.20",
14 | "@react-email/render": "^0.0.16",
15 | "@types/react": "^18.3.3",
16 | "react": "^18.3.1",
17 | "react-email": "2.1.5"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/emails/readme.md:
--------------------------------------------------------------------------------
1 | # React Email Starter
2 |
3 | A live preview right in your browser so you don't need to keep sending real emails during development.
4 |
5 | ## Getting Started
6 |
7 | First, install the dependencies:
8 |
9 | ```sh
10 | npm install
11 | # or
12 | yarn
13 | ```
14 |
15 | Then, run the development server:
16 |
17 | ```sh
18 | npm run dev
19 | # or
20 | yarn dev
21 | ```
22 |
23 | Open [localhost:3000](http://localhost:3000) with your browser to see the result.
24 |
25 | ## License
26 |
27 | MIT License
28 |
--------------------------------------------------------------------------------
/packages/emails/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "target": "ESNext",
5 | "module": "ESNext",
6 | "moduleResolution": "node",
7 | "allowSyntheticDefaultImports": true,
8 | "esModuleInterop": true,
9 | "resolveJsonModule": true,
10 | "jsx": "react",
11 | "baseUrl": ".",
12 | "paths": {
13 | "#*": ["./src/*"]
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/scripts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vrite/scripts",
3 | "private": true,
4 | "dependencies": {
5 | "chalk": "^5.3.0",
6 | "commander": "^12.0.0",
7 | "esbuild": "^0.20.0",
8 | "glob": "^10.3.10",
9 | "image-type": "^5.2.0"
10 | },
11 | "bin": "development.mjs"
12 | }
13 |
--------------------------------------------------------------------------------
/packages/sdk/javascript/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Vrite, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/sdk/javascript/build.config.ts:
--------------------------------------------------------------------------------
1 | import { defineBuildConfig } from "unbuild";
2 |
3 | export default defineBuildConfig({
4 | entries: [
5 | { input: "./src/api" },
6 | { input: "./src/transformers" },
7 | { input: "./src/extensions" },
8 | { input: "./src/astro", builder: "mkdist", outDir: "dist/astro" },
9 | { input: "./src/types", builder: "mkdist", outDir: "dist/types" }
10 | ],
11 | declaration: true,
12 | clean: true,
13 | failOnWarn: false,
14 | rollup: {
15 | emitCJS: true,
16 | esbuild: {
17 | minify: true
18 | }
19 | }
20 | });
21 |
--------------------------------------------------------------------------------
/packages/sdk/javascript/src/api/errors.ts:
--------------------------------------------------------------------------------
1 | interface UnauthorizedError {
2 | message: string;
3 | code: "UNAUTHORIZED";
4 | }
5 | interface BadRequestError {
6 | message: string;
7 | code: "BAD_REQUEST";
8 | issues: Array<{
9 | code: string;
10 | path: string[];
11 | message: string;
12 |
13 | expected?: string;
14 | received?: string;
15 | validation?: string;
16 | }>;
17 | }
18 |
19 | type APIError = UnauthorizedError | BadRequestError;
20 |
21 | export type { UnauthorizedError, BadRequestError, APIError };
22 |
--------------------------------------------------------------------------------
/packages/sdk/javascript/src/api/extension.ts:
--------------------------------------------------------------------------------
1 | import { SendRequest } from "./request";
2 |
3 | // eslint-disable-next-line no-use-before-define
4 | type ContextValue = string | number | boolean | ContextObject | ContextArray;
5 |
6 | interface ContextObject {
7 | [x: string]: ContextValue;
8 | }
9 | interface ContextArray extends Array {}
10 | interface Extension {
11 | id: string;
12 | name: string;
13 | url: string;
14 | config: ContextObject;
15 | token: string;
16 | }
17 | interface ExtensionEndpoints {
18 | get(): Promise>;
19 | updateContentPieceData(input: { contentPieceId: string; data: ContextObject }): Promise;
20 | }
21 |
22 | const basePath = "/extension";
23 | const createExtensionEndpoints = (sendRequest: SendRequest): ExtensionEndpoints => ({
24 | get: () => {
25 | return sendRequest>("GET", `${basePath}`);
26 | },
27 | updateContentPieceData: (input) => {
28 | return sendRequest("POST", `${basePath}/content-piece-data`, { body: input });
29 | }
30 | });
31 |
32 | export { createExtensionEndpoints };
33 | export type { Extension, ExtensionEndpoints };
34 |
--------------------------------------------------------------------------------
/packages/sdk/javascript/src/api/index.ts:
--------------------------------------------------------------------------------
1 | export { createClient } from "./client";
2 | export type { Client, SearchResult } from "./client";
3 | export type { ContentGroup, ContentGroupWithSubtree } from "./content-groups";
4 | export type {
5 | ContentPiece,
6 | ContentPieceWithAdditionalData,
7 | JSONContent,
8 | JSONContentAttrs
9 | } from "./content-pieces";
10 | export type { Snippet } from "./snippets";
11 | export type { Profile } from "./profile";
12 | export type { Role, RoleBaseType, RolePermission } from "./roles";
13 | export type { Tag, TagColor } from "./tags";
14 | export type { UIAccentColor, UserSettings } from "./user-settings";
15 | export type { Webhook, WebhookEvent } from "./webhooks";
16 | export type { WorkspaceDetails } from "./workspace";
17 | export type { Block, Embed, Mark, WorkspaceSettings } from "./workspace-settings";
18 | export type { ListedMember, ListedWorkspace } from "./workspace-memberships";
19 | export type { Extension } from "./extension";
20 | export type { Variant } from "./variants";
21 | export * from "./errors";
22 |
--------------------------------------------------------------------------------
/packages/sdk/javascript/src/api/user-settings.ts:
--------------------------------------------------------------------------------
1 | import { SendRequest } from "./request";
2 |
3 | type UIAccentColor = "energy" | "neon" | "sublime" | "sunrise" | "flow";
4 | interface UserSettings {
5 | /**
6 | * UI Theme
7 | */
8 | uiTheme: "light" | "dark" | "auto";
9 | /**
10 | * Code editor theme
11 | */
12 | codeEditorTheme: "light" | "dark" | "auto";
13 | /**
14 | * UI accent color
15 | */
16 | accentColor: UIAccentColor;
17 | }
18 | interface UserSettingsEndpoints {
19 | get(): Promise;
20 | update(input: Partial): Promise;
21 | }
22 |
23 | const basePath = "/user-settings";
24 | const createUserSettingsEndpoints = (sendRequest: SendRequest): UserSettingsEndpoints => ({
25 | get: () => {
26 | return sendRequest("GET", `${basePath}`);
27 | },
28 | update: (input) => {
29 | return sendRequest("PUT", `${basePath}`, {
30 | body: input
31 | });
32 | }
33 | });
34 |
35 | export { createUserSettingsEndpoints };
36 | export type { UserSettings, UIAccentColor, UserSettingsEndpoints };
37 |
--------------------------------------------------------------------------------
/packages/sdk/javascript/src/api/workspace.ts:
--------------------------------------------------------------------------------
1 | import { SendRequest } from "./request";
2 |
3 | interface WorkspaceDetails {
4 | /**
5 | * Workspace ID
6 | */
7 | id: string;
8 | /**
9 | * Workspace name
10 | */
11 | name: string;
12 | /**
13 | * Workspace description
14 | */
15 | description?: string;
16 | /**
17 | * Workspace logo URL
18 | */
19 | logo?: string;
20 | }
21 | interface WorkspaceEndpoints {
22 | get(): Promise;
23 | }
24 |
25 | const basePath = "/workspace";
26 | const createWorkspaceEndpoints = (sendRequest: SendRequest): WorkspaceEndpoints => ({
27 | get: () => {
28 | return sendRequest("GET", `${basePath}`);
29 | }
30 | });
31 |
32 | export { createWorkspaceEndpoints };
33 | export type { WorkspaceDetails, WorkspaceEndpoints };
34 |
--------------------------------------------------------------------------------
/packages/sdk/javascript/src/astro/content.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { client, getContentGroupId } from "virtual:vrite";
3 | import type { Client, JSONContent } from "../api";
4 | import Node from "./node.astro";
5 |
6 | interface Props {
7 | contentPieceId?: string;
8 | content?: JSONContent;
9 | slug?: string;
10 | variant?: string;
11 | }
12 |
13 | let content: JSONContent | null = null;
14 |
15 | if (Astro.props.content) {
16 | content = Astro.props.content;
17 | } else {
18 | let contentPieceId = Astro.props.contentPieceId;
19 |
20 | if (!contentPieceId && Astro.props.slug) {
21 | const contentPieces = await (client as Client).contentPieces.list({
22 | contentGroupId: getContentGroupId(),
23 | slug: Astro.props.slug
24 | });
25 | if (contentPieces[0]) {
26 | contentPieceId = contentPieces[0].id;
27 | }
28 | }
29 |
30 | if (contentPieceId) {
31 | const contentPiece = await (client as Client).contentPieces.get({
32 | id: contentPieceId,
33 | content: true,
34 | description: "text",
35 | variant: Astro.props.variant
36 | });
37 |
38 | content = contentPiece.content;
39 | }
40 | }
41 | ---
42 |
43 | {content && }
44 |
--------------------------------------------------------------------------------
/packages/sdk/javascript/src/transformers/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./html";
2 | export * from "./gfm";
3 | export * from "./transformer";
4 | export * from "./content-walker";
5 | export * from "./output-transformers";
6 | export * from "./input-transformers";
7 |
--------------------------------------------------------------------------------
/packages/sdk/javascript/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "resolveJsonModule": true,
7 | "esModuleInterop": true,
8 | "outDir": "dist",
9 | "strict": true,
10 | "declaration": true,
11 | "isolatedModules": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "apps/*"
3 | - "apps/**/*"
4 | - "packages/*"
5 | - "packages/extensions/*"
6 | - "packages/sdk/*"
7 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "globalDependencies": ["**/.env.*local"],
4 | "ui": "stream",
5 | "tasks": {
6 | "build": {
7 | "dependsOn": ["^build"],
8 | "cache": false
9 | },
10 | "@vrite/app#build": {
11 | "dependsOn": ["@vrite/web#build"]
12 | },
13 | "lint": {
14 | "outputs": []
15 | },
16 | "dev": {
17 | "cache": false
18 | },
19 | "start": {
20 | "outputs": []
21 | },
22 | "publish": {
23 | "outputs": []
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------