From 57d66590bc6402047493656aa34775bf832c8c7a Mon Sep 17 00:00:00 2001 From: Lewis Carhart Date: Fri, 14 Feb 2025 14:47:55 +0000 Subject: [PATCH 1/2] feat: editor switch to liveblocks --- .../(dashboard)/policies/[id]/layout.tsx | 2 +- .../(app)/(dashboard)/policies/[id]/page.tsx | 79 ++++++++++++------- .../(dashboard)/policies/[id]/providers.tsx | 26 +++++- .../(app)/(dashboard)/policies/[id]/room.tsx | 19 ----- .../src/components/editor/advanced-editor.tsx | 15 +++- apps/app/src/components/editor/extensions.ts | 2 +- .../editor/selectors/add-comment-selector.tsx | 2 +- .../src/components/editor/slash-command.tsx | 1 - apps/app/src/components/editor/threads.tsx | 26 +----- .../components/policies/policy-overview.tsx | 2 +- packages/ui/src/globals.css | 4 +- packages/ui/src/text-editor.css | 32 ++++++-- 12 files changed, 120 insertions(+), 90 deletions(-) delete mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/room.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/layout.tsx index 351e1c0d9a..756f4e5049 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/layout.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/layout.tsx @@ -27,7 +27,7 @@ export default async function Layout({ return (
- +
{children}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/page.tsx index b232f9aa54..7bf99bf417 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/page.tsx @@ -1,34 +1,59 @@ +"use client"; + +import AdvancedEditor from "@/components/editor/advanced-editor"; +import { DocumentSpinner } from "@/components/editor/spinner"; import { PolicyOverview } from "@/components/policies/policy-overview"; -import { getI18n } from "@/locales/server"; -import type { Metadata } from "next"; -import { setStaticParamsLocale } from "next-international/server"; -import { Room } from "./room"; +import { Button } from "@bubba/ui/button"; +import { Separator } from "@bubba/ui/separator"; +import { ClientSideSuspense } from "@liveblocks/react"; +import { useAction } from "next-safe-action/hooks"; +import { useParams } from "next/navigation"; +import type { JSONContent } from "novel"; +import { toast } from "sonner"; +import { usePolicy } from "../hooks/usePolicy"; +import { publishPolicy } from "./actions/publish-policy"; -interface PageProps { - params: Promise<{ locale: string; id: string }>; -} +export default function PolicyPage() { + const { id } = useParams(); + + const { execute, isExecuting } = useAction( + () => publishPolicy({ id: id as string }), + { + onSuccess: () => { + toast.success("Policy published successfully"); + }, + }, + ); + + const { data: policy } = usePolicy({ policyId: id as string }); -export default async function PolicyPage({ params }: PageProps) { - const { locale, id } = await params; - setStaticParamsLocale(locale); + if (!policy) return null; + + const content = policy.content as JSONContent; + + if (!content) return null; return ( - - - +
+
+ +
+ +
+
+
+ }> + + +
+
+
+
); } - -export async function generateMetadata({ - params, -}: { - params: Promise<{ locale: string; id: string }>; -}): Promise { - const { locale } = await params; - setStaticParamsLocale(locale); - const t = await getI18n(); - - return { - title: t("sub_pages.policies.editor"), - }; -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/providers.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/providers.tsx index 063ef2f5c9..d5f6a26598 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/providers.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/providers.tsx @@ -1,9 +1,27 @@ "use client"; -import { LiveblocksProvider } from "@liveblocks/react"; +import { LiveblocksProvider, RoomProvider } from "@liveblocks/react"; +import { useSession } from "next-auth/react"; +import { redirect } from "next/navigation"; +import type { JSONContent } from "novel"; import type { PropsWithChildren } from "react"; +import { usePolicy } from "../hooks/usePolicy"; + +interface ProvidersProps extends PropsWithChildren { + policyId: string; +} + +export function Providers({ children, policyId }: ProvidersProps) { + const { data: session } = useSession(); + const { data: policy } = usePolicy({ policyId }); + + if (!policyId || !session?.user?.organizationId) { + redirect("/policies"); + } + + const content = policy?.content as JSONContent; + const roomId = `liveblocks:policies:${session.user.organizationId}:${policyId}`; -export function Providers({ children }: PropsWithChildren) { return ( - {children} + + {children} + ); } diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/room.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/room.tsx deleted file mode 100644 index 6fe27cbdab..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/room.tsx +++ /dev/null @@ -1,19 +0,0 @@ -"use client"; - -import { RoomProvider } from "@liveblocks/react/suspense"; -import { useSession } from "next-auth/react"; -import { redirect, useParams } from "next/navigation"; -import type { ReactNode } from "react"; - -export function Room({ children }: { children: ReactNode }) { - const { id: policyId } = useParams(); - const { data: session } = useSession(); - - if (!policyId || !session?.user?.organizationId) { - return redirect("/policies"); - } - - const roomId = `liveblocks:policies:${session.user.organizationId}:${policyId}`; - - return {children}; -} diff --git a/apps/app/src/components/editor/advanced-editor.tsx b/apps/app/src/components/editor/advanced-editor.tsx index ab67481291..7c9973764b 100644 --- a/apps/app/src/components/editor/advanced-editor.tsx +++ b/apps/app/src/components/editor/advanced-editor.tsx @@ -3,7 +3,7 @@ import { Separator } from "@bubba/ui/separator"; import { useLiveblocksExtension } from "@liveblocks/react-tiptap"; import { useSyncStatus } from "@liveblocks/react/suspense"; -import type { Extensions } from "@tiptap/react"; +import type { Extensions, JSONContent } from "@tiptap/react"; import { EditorCommand, EditorCommandEmpty, @@ -11,7 +11,6 @@ import { EditorCommandList, EditorContent, EditorRoot, - type JSONContent, } from "novel"; import { ImageResizer, handleCommandNavigation } from "novel"; import { handleImageDrop, handleImagePaste } from "novel"; @@ -19,15 +18,20 @@ import { useState } from "react"; import { defaultExtensions } from "./extensions"; import GenerativeMenuSwitch from "./generative/generative-menu-switch"; import { uploadFn } from "./image-upload"; +import { AddCommentSelector } from "./selectors/add-comment-selector"; import { ColorSelector } from "./selectors/color-selector"; import { LinkSelector } from "./selectors/link-selector"; import { MathSelector } from "./selectors/math-selector"; import { NodeSelector } from "./selectors/node-selector"; import { TextButtons } from "./selectors/text-buttons"; import { slashCommand, suggestionItems } from "./slash-command"; +import { Threads } from "./threads"; export default function AdvancedEditor() { - const liveblocks = useLiveblocksExtension(); + const liveblocks = useLiveblocksExtension({ + offlineSupport_experimental: true, + }); + const extensions: Extensions = [ ...defaultExtensions, slashCommand, @@ -61,7 +65,7 @@ export default function AdvancedEditor() { handleCommandNavigation(event), @@ -81,6 +85,7 @@ export default function AdvancedEditor() { slotAfter={} immediatelyRender={false} > + No results @@ -124,6 +129,8 @@ export default function AdvancedEditor() { + + diff --git a/apps/app/src/components/editor/extensions.ts b/apps/app/src/components/editor/extensions.ts index c91796c17e..d17230c16c 100644 --- a/apps/app/src/components/editor/extensions.ts +++ b/apps/app/src/components/editor/extensions.ts @@ -34,7 +34,7 @@ const tiptapLink = TiptapLink.configure({ const taskList = TaskList.configure({ HTMLAttributes: { - class: cx("not-prose pl-2 "), + class: cx("not-prose pl-2"), }, }); const taskItem = TaskItem.configure({ diff --git a/apps/app/src/components/editor/selectors/add-comment-selector.tsx b/apps/app/src/components/editor/selectors/add-comment-selector.tsx index 7117dde286..e440b576cb 100644 --- a/apps/app/src/components/editor/selectors/add-comment-selector.tsx +++ b/apps/app/src/components/editor/selectors/add-comment-selector.tsx @@ -1,6 +1,6 @@ import { Button } from "@bubba/ui/button"; import { cn } from "@bubba/ui/cn"; -import { MessageSquarePlus, SigmaIcon } from "lucide-react"; +import { MessageSquarePlus } from "lucide-react"; import { useEditor } from "novel"; export const AddCommentSelector = () => { diff --git a/apps/app/src/components/editor/slash-command.tsx b/apps/app/src/components/editor/slash-command.tsx index a8579bf33e..27c7bc5628 100644 --- a/apps/app/src/components/editor/slash-command.tsx +++ b/apps/app/src/components/editor/slash-command.tsx @@ -14,7 +14,6 @@ import { } from "lucide-react"; import { createSuggestionItems } from "novel"; import { Command, renderItems } from "novel"; -import { uploadFn } from "./image-upload"; export const suggestionItems = createSuggestionItems([ { diff --git a/apps/app/src/components/editor/threads.tsx b/apps/app/src/components/editor/threads.tsx index efa0a26fd9..d2826443ca 100644 --- a/apps/app/src/components/editor/threads.tsx +++ b/apps/app/src/components/editor/threads.tsx @@ -1,18 +1,10 @@ -import { - AnchoredThreads, - FloatingComposer, - FloatingThreads, -} from "@liveblocks/react-tiptap"; +import { FloatingComposer, FloatingThreads } from "@liveblocks/react-tiptap"; import { useThreads } from "@liveblocks/react/suspense"; import { useEditor } from "novel"; import { useSyncExternalStore } from "react"; -import "@bubba/ui/globals.css"; -import "@bubba/ui/prosemirror"; -import "@bubba/ui/text-editor"; export function Threads() { const { editor } = useEditor(); - const isMobile = useIsMobile(); const { threads } = useThreads({ query: { resolved: false } }); if (!editor) { @@ -21,20 +13,8 @@ export function Threads() { return ( <> - - {isMobile ? ( - - ) : ( - - )} + + ); } diff --git a/apps/app/src/components/policies/policy-overview.tsx b/apps/app/src/components/policies/policy-overview.tsx index 234459d84d..e746f198b5 100644 --- a/apps/app/src/components/policies/policy-overview.tsx +++ b/apps/app/src/components/policies/policy-overview.tsx @@ -44,7 +44,7 @@ export function PolicyOverview({ policyId }: { policyId: string }) {
}> - +
diff --git a/packages/ui/src/globals.css b/packages/ui/src/globals.css index af37c97294..3c3512abc5 100644 --- a/packages/ui/src/globals.css +++ b/packages/ui/src/globals.css @@ -332,11 +332,11 @@ html.grabbing * { .lb-root { --lb-foreground: hsl(var(--foreground)); - --lb-background: hsl(var(--background)); + --lb-background: hsl(var(--foreground)); &.lb-elevation, &.lb-tooltip { - --lb-background: hsl(var(--background)); + --lb-background: hsl(var(--foreground)); } } diff --git a/packages/ui/src/text-editor.css b/packages/ui/src/text-editor.css index 734d56b2fb..b538345a09 100644 --- a/packages/ui/src/text-editor.css +++ b/packages/ui/src/text-editor.css @@ -207,13 +207,31 @@ h3 .tiptap-code { border: 0; } -.collaboration-cursor__label { - font-weight: 500; - font-family: var(--font-sans); - z-index: 10000; +/* Give a remote user a caret */ +.collaboration-cursor__caret { + border-left: 1px solid #0d0d0d; + border-right: 1px solid #0d0d0d; + margin-left: -1px; + margin-right: -1px; + pointer-events: none; + position: relative; + word-break: normal; } -/* Fixes Tailwind Prose bug with hidden cursor in code blocks */ -.prose :where(pre):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - overflow-x: visible; +/* Render the username above the caret */ +.collaboration-cursor__label { + font-style: normal; + font-weight: 600; + left: -1px; + line-height: normal; + position: absolute; + user-select: none; + white-space: nowrap; + font-size: 14px; + color: #fff; + top: -1.4em; + border-radius: 6px; + border-bottom-left-radius: 0; + padding: 2px 6px; + pointer-events: none; } \ No newline at end of file From 260c6dcffb803ad847cb99d667536187074950e1 Mon Sep 17 00:00:00 2001 From: Lewis Carhart Date: Sat, 15 Feb 2025 20:34:35 +0000 Subject: [PATCH 2/2] feat: fixed web --- apps/web/src/app/(home)/layout.tsx | 4 +- apps/web/src/app/(home)/page.tsx | 8 +- apps/web/src/app/components/waitlist-form.tsx | 1 - apps/web/src/app/layout.tsx | 13 +-- packages/ui/src/globals.css | 104 ----------------- packages/ui/src/text-editor.css | 105 +++++++++++++++++- 6 files changed, 111 insertions(+), 124 deletions(-) diff --git a/apps/web/src/app/(home)/layout.tsx b/apps/web/src/app/(home)/layout.tsx index 2621cc6dd8..1d94561bf6 100644 --- a/apps/web/src/app/(home)/layout.tsx +++ b/apps/web/src/app/(home)/layout.tsx @@ -7,9 +7,9 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( -
+
-
{children}
+
{children}
); diff --git a/apps/web/src/app/(home)/page.tsx b/apps/web/src/app/(home)/page.tsx index bc3c80df45..fe5d6440bd 100644 --- a/apps/web/src/app/(home)/page.tsx +++ b/apps/web/src/app/(home)/page.tsx @@ -15,10 +15,10 @@ export const metadata: Metadata = { export default function Home() { return ( -
-
-
- +
+
+
+

Get SOC 2, ISO 27001 and GDPR certified diff --git a/apps/web/src/app/components/waitlist-form.tsx b/apps/web/src/app/components/waitlist-form.tsx index 0cdbda3e6b..8247916ef9 100644 --- a/apps/web/src/app/components/waitlist-form.tsx +++ b/apps/web/src/app/components/waitlist-form.tsx @@ -80,7 +80,6 @@ export function WaitlistForm() { diff --git a/packages/ui/src/globals.css b/packages/ui/src/globals.css index 3c3512abc5..1186d4b92f 100644 --- a/packages/ui/src/globals.css +++ b/packages/ui/src/globals.css @@ -306,107 +306,3 @@ .aria-selected\:text-accent-foreground[aria-selected='true'] { border-radius: 0px; } - -html, -body { - --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, - Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; - font-family: var(--font-sans); -} - -html, -body, -#__next { - max-width: 100%; - min-height: 100vh; - overflow: hidden; -} - -html.grabbing { - cursor: grabbing; -} - -html.grabbing * { - user-select: none; -} - -.lb-root { - --lb-foreground: hsl(var(--foreground)); - --lb-background: hsl(var(--foreground)); - - &.lb-elevation, - &.lb-tooltip { - --lb-background: hsl(var(--foreground)); - } -} - -.dark .lb-root { - --lb-inset-shadow: inset 0 0 0 1px hsl(var(--border)); -} - -pre { - background: #0d0d0d; - border-radius: 0.5rem; - color: #fff; - font-family: "JetBrainsMono", monospace; - padding: 0.75rem 1rem; - - code { - background: none; - color: inherit; - font-size: 0.8rem; - padding: 0; - } - - .hljs-comment, - .hljs-quote { - color: #616161; - } - - .hljs-variable, - .hljs-template-variable, - .hljs-attribute, - .hljs-tag, - .hljs-name, - .hljs-regexp, - .hljs-link, - .hljs-name, - .hljs-selector-id, - .hljs-selector-class { - color: #f98181; - } - - .hljs-number, - .hljs-meta, - .hljs-built_in, - .hljs-builtin-name, - .hljs-literal, - .hljs-type, - .hljs-params { - color: #fbbc88; - } - - .hljs-string, - .hljs-symbol, - .hljs-bullet { - color: #b9f18d; - } - - .hljs-title, - .hljs-section { - color: #faf594; - } - - .hljs-keyword, - .hljs-selector-tag { - color: #70cff8; - } - - .hljs-emphasis { - font-style: italic; - } - - .hljs-strong { - font-weight: 700; - } -} diff --git a/packages/ui/src/text-editor.css b/packages/ui/src/text-editor.css index b538345a09..f1d3d7f536 100644 --- a/packages/ui/src/text-editor.css +++ b/packages/ui/src/text-editor.css @@ -234,4 +234,107 @@ h3 .tiptap-code { border-bottom-left-radius: 0; padding: 2px 6px; pointer-events: none; -} \ No newline at end of file +} +html, +body { + --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, + Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + font-family: var(--font-sans); +} + +html, +body, +#__next { + max-width: 100%; + min-height: 100vh; + overflow: hidden; +} + +html.grabbing { + cursor: grabbing; +} + +html.grabbing * { + user-select: none; +} + +.lb-root { + --lb-foreground: hsl(var(--foreground)); + --lb-background: hsl(var(--foreground)); + + &.lb-elevation, + &.lb-tooltip { + --lb-background: hsl(var(--foreground)); + } +} + +.dark .lb-root { + --lb-inset-shadow: inset 0 0 0 1px hsl(var(--border)); +} + +pre { + background: #0d0d0d; + border-radius: 0.5rem; + color: #fff; + font-family: "JetBrainsMono", monospace; + padding: 0.75rem 1rem; + + code { + background: none; + color: inherit; + font-size: 0.8rem; + padding: 0; + } + + .hljs-comment, + .hljs-quote { + color: #616161; + } + + .hljs-variable, + .hljs-template-variable, + .hljs-attribute, + .hljs-tag, + .hljs-name, + .hljs-regexp, + .hljs-link, + .hljs-name, + .hljs-selector-id, + .hljs-selector-class { + color: #f98181; + } + + .hljs-number, + .hljs-meta, + .hljs-built_in, + .hljs-builtin-name, + .hljs-literal, + .hljs-type, + .hljs-params { + color: #fbbc88; + } + + .hljs-string, + .hljs-symbol, + .hljs-bullet { + color: #b9f18d; + } + + .hljs-title, + .hljs-section { + color: #faf594; + } + + .hljs-keyword, + .hljs-selector-tag { + color: #70cff8; + } + + .hljs-emphasis { + font-style: italic; + } + + .hljs-strong { + font-weight: 700; + } +}