├── .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 | ![Cover image](public/meta-image.png) 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 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 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 | {props.alt} 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 | ![Cover image](public/meta-image.png) 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 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 | {props.alt} 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 |
7 | 11 |
12 |
13 | 14 |
15 |
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 |
7 | 11 |
12 |
13 | 14 |
15 |
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 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 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 | 20 | 21 | 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 | 29 | 30 | 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 | --------------------------------------------------------------------------------