Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/sitemap.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Generate Sitemap

on:
push:
branches:
- main
paths:
- "docs/**"
workflow_dispatch:

permissions:
contents: write

jobs:
sitemap:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Generate Sitemap
uses: tenelabs/docsify-sitemap-action@v1
with:
baseUrl: https://docs.chaibuilder.com
docsPath: ./docs
sitemapPath: ./docs/sitemap.xml

- name: Commit and Push
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add docs/sitemap.xml
git diff --quiet && git diff --staged --quiet || (git commit -m "chore: update sitemap" && git push)
2 changes: 1 addition & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
loadSidebar: true,
subMaxLevel: 3,
auto2top: true,
routerMode: "history",
routerMode: "hash",
search: {
placeholder: "Search docs...",
noData: "No results found",
Expand Down
3 changes: 3 additions & 0 deletions src/atoms/ui.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { defaultThemeValues } from "@/hooks/default-theme-options";
import { ChaiTheme } from "@/types";
import { atom } from "jotai";
import { atomWithStorage } from "jotai/utils";
import { TreeApi } from "react-arborist";
Expand Down Expand Up @@ -58,3 +60,4 @@ selectedLibraryAtom.debugLabel = "selectedLibraryAtom";

export const dataBindingActiveAtom = atom(true);
dataBindingActiveAtom.debugLabel = "dataBindingActiveAtom";
export const lsThemeAtom = atomWithStorage<ChaiTheme>("chai-builder-theme", defaultThemeValues);
2 changes: 1 addition & 1 deletion src/core/rjsf-widgets/rte-widget/rte-color-picker.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { lsThemeAtom } from "@/atoms/ui";
import { Input } from "@/components/ui/input";
import { cn } from "@/core/utils/cn";
import { useDarkMode } from "@/hooks/use-dark-mode";
import { lsThemeAtom } from "@/routes/demo/atoms-dev";
import { CaretDownIcon, Cross1Icon } from "@radix-ui/react-icons";
import { useDebouncedState } from "@react-hookz/web";
import { useAtom } from "jotai";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Dialog,
Expand All @@ -14,6 +15,7 @@ interface UnpublishedPartialsModalProps {
isOpen: boolean;
onClose: () => void;
onContinue: () => void;
onViewChanges?: (partialId: string, partialName: string) => void;
isPending?: boolean;
partialBlocksInfo?: PartialBlockInfo[];
}
Expand All @@ -22,6 +24,7 @@ const UnpublishedPartialsModal = ({
isOpen,
onClose,
onContinue,
onViewChanges,
isPending = false,
partialBlocksInfo = [],
}: UnpublishedPartialsModalProps) => {
Expand All @@ -31,10 +34,10 @@ const UnpublishedPartialsModal = ({
{isOpen && (
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>{t("Publish Page with Unpublished Blocks?")}</DialogTitle>
<DialogTitle>{t("You have some unpublished changes")}</DialogTitle>
<DialogDescription>
{t(
"You have unpublished changes in the following blocks. They will be published together with the page.",
"The following partials are either unpublished or have unpublished changes.",
)}
</DialogDescription>
</DialogHeader>
Expand All @@ -43,12 +46,29 @@ const UnpublishedPartialsModal = ({
<ul className="space-y-1 text-sm">
{partialBlocksInfo.map((info) => (
<li key={info?.id} className="flex items-center justify-between text-muted-foreground">
<span>• {info?.name}</span>
<span
className={`ml-2 rounded px-1.5 pb-0.5 text-[10px] ${
info?.status === "unpublished" ? "text-orange-700" : "text-blue-700"
}`}>
{info?.status === "unpublished" ? t("Unpublished page") : t("Unpublished changes")}
<span className="flex items-center gap-2"><p className={`h-2 w-2 rounded-full ${info?.status === "unpublished_changes" ? "bg-green-400" : "bg-gray-300"}`}></p> {info?.name}</span>
<span className="flex items-center gap-1">
{info?.status === "unpublished_changes" && (
<>
<Badge className="bg-green-100 text-green-600 border-green-200 hover:bg-green-100 text-[10px] px-1.5 py-0">
{t("Published")}
</Badge>
{onViewChanges && (
<Button
variant="ghost"
title={t("View Changes")}
className="text-blue-600 hover:text-blue-600 hover:bg-transparent hover:underline text-[10px] p-0"
onClick={() => onViewChanges(info.id, info.name)}>
{t("View Changes")}
</Button>
)}
</>
)}
{info?.status === "unpublished" && (
<Badge className="bg-orange-100 text-orange-600 border-orange-200 hover:bg-orange-100 text-[10px] px-1.5 py-0">
{t("Unpublished")}
</Badge>
)}
</span>
</li>
))}
Expand All @@ -60,7 +80,7 @@ const UnpublishedPartialsModal = ({
{t("Cancel")}
</Button>
<Button onClick={onContinue} disabled={isPending}>
{isPending ? t("Publishing...") : t("Publish Page & Blocks")}
{isPending ? t("Publishing...") : t("Publish Partials & Page")}
</Button>
</DialogFooter>
</DialogContent>
Expand Down
47 changes: 46 additions & 1 deletion src/pages/client/components/topbar-right.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import PermissionChecker from "@/pages/client/components/permission-checker";
import PublishPages from "@/pages/client/components/publish-pages/publish-pages";
import { PAGES_PERMISSIONS } from "@/pages/constants/PERMISSIONS";
import { usePublishPages } from "@/pages/hooks/pages/mutations";
import { useCurrentActivePage, usePrimaryPage } from "@/pages/hooks/pages/use-current-page";
import { useCurrentActivePage, useGetPageFullSlug, usePrimaryPage } from "@/pages/hooks/pages/use-current-page";
import { useGetUnpublishedPartialBlocks } from "@/pages/hooks/pages/use-get-unpublished-partial-blocks";
import { useIsLanguagePageCreated } from "@/pages/hooks/pages/use-is-languagep-page-created";
import { useLanguagePages } from "@/pages/hooks/pages/use-language-pages";
Expand All @@ -30,6 +30,7 @@ import { compact, find, isEmpty, upperCase } from "lodash-es";
import {
CheckCircle,
ChevronDown,
ExternalLink,
Eye,
Loader,
Palette,
Expand Down Expand Up @@ -195,6 +196,7 @@ const PublishButton = () => {
const [showUnpublishedPartialsWarning, setShowUnpublishedPartialsWarning] = useState(false);
const [unpublishedPartialBlockIds, setUnpublishedPartialBlockIds] = useState<string[]>([]);
const [unpublishedPartialBlocksInfo, setUnpublishedPartialBlocksInfo] = useState<any[]>([]);
const [comparePartial, setComparePartial] = useState<{ id: string; name: string } | null>(null);

const { data: currentPage } = usePrimaryPage();
const { mutate: publishPage, isPending } = usePublishPages();
Expand Down Expand Up @@ -261,6 +263,10 @@ const PublishButton = () => {
setUnpublishedPartialBlocksInfo([]);
};

const handleViewPartialChanges = useCallback((partialId: string, partialName: string) => {
setComparePartial({ id: partialId, name: partialName });
}, []);

const handleContinueAnyway = () => {
setShowTranslationWarning(false);
checkAndPublish([activePage?.id, activePage?.primaryPage]);
Expand Down Expand Up @@ -413,15 +419,53 @@ const PublishButton = () => {
isOpen={showUnpublishedPartialsWarning}
onClose={handleCancelPartials}
onContinue={handleContinueWithPartials}
onViewChanges={handleViewPartialChanges}
isPending={isPending}
partialBlocksInfo={unpublishedPartialBlocksInfo}
/>
</Suspense>
)}

{comparePartial && (
<Suspense>
<JsonDiffViewer
open={!!comparePartial}
onOpenChange={(open) => {
if (!open) {
setComparePartial(null);
setShowUnpublishedPartialsWarning(true);
}
}}
compare={[
{ label: "live", uid: `live:${comparePartial.id}`, item: {} },
{ label: "draft", uid: `draft:${comparePartial.id}`, item: {} },
]}
/>
</Suspense>
)}
</>
);
};

const LiveLinkButton = () => {
const { t } = useTranslation();
const { data: currentPage } = usePrimaryPage();
const fullUrl = useGetPageFullSlug();
const isOnline = currentPage?.online;

if (!isOnline) return null;

return (
<Tooltip content={t("Open live page")} delayDuration={0}>
<a href={fullUrl} target="_blank" rel="noopener noreferrer">
<Button variant="ghost" size="icon" className="ml-1 h-8 w-8">
<ExternalLink className="h-4 w-4" />
</Button>
</a>
</Tooltip>
);
};

export default function TopbarRight() {
const { isLocked } = usePageLockStatus();
const [searchParams] = useSearchParams();
Expand All @@ -443,6 +487,7 @@ export default function TopbarRight() {
<PermissionChecker permission={PAGES_PERMISSIONS.PUBLISH_PAGE}>
<PublishButton />
</PermissionChecker>
<LiveLinkButton />
</div>
);
}
57 changes: 4 additions & 53 deletions src/pages/extensions/topbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ import { PageDropdownInHeader } from "@/pages/client/components/page-dropdown-in
import { ScreenOverlay } from "@/pages/client/components/screen-overlay";
import TopbarLeft, { LanguageSwitcher } from "@/pages/client/components/topbar-left";
import TopbarRight from "@/pages/client/components/topbar-right";
import { useCurrentActivePage, useGetPageFullSlug, usePrimaryPage } from "@/pages/hooks/pages/use-current-page";
import { useDynamicPageSelector, useDynamicPageSlug } from "@/pages/hooks/pages/use-dynamic-page-selector";
import { useCurrentActivePage, usePrimaryPage } from "@/pages/hooks/pages/use-current-page";
import { useDynamicPageSelector } from "@/pages/hooks/pages/use-dynamic-page-selector";
import { useChaiFeatureFlag } from "@/runtime/client";
import { get } from "lodash-es";
import { ChevronRight, ExternalLink } from "lucide-react";
import { ChevronRight } from "lucide-react";
import { lazy, Suspense } from "react";
import { useTranslation } from "react-i18next";
import PagesManagerTrigger from "../client/components/page-manager/page-manager-trigger";
import Tooltip from "../utils/tooltip";
const DynamicPageSelector = lazy(() => import("../client/components/dynamic-page-selector"));

const DynamicPageSelectorSuspense = () => {
Expand All @@ -31,21 +29,12 @@ const DynamicPageSelectorSuspense = () => {
};

const AddressBar = () => {
const { data: activePage, isFetching: isFetchingActivePage } = useCurrentActivePage();
const { isFetching: isFetchingActivePage } = useCurrentActivePage();
const { data: page, isFetching: isFetchingCurrentPage } = usePrimaryPage();
const dynamic = get(page, "dynamic", false);
const dynamicPageSlug = useDynamicPageSlug();
const isDynamicPageSelectorEnabled = useChaiFeatureFlag("dynamic-page-selector");
const { t } = useTranslation();
const slug = activePage?.slug;
const isPartialPage = !slug;
const fullUrl = useGetPageFullSlug();
const isFetching = isFetchingActivePage || isFetchingCurrentPage;

// Ensure the slug is always visible, truncate domain if needed
const visible = isPartialPage ? `Partial: ${activePage?.name} ` : `${slug}${dynamicPageSlug}`;
const visibleSlug = visible.replace(window.location.host, "");

return (
<div className={`relative flex items-center`}>
<div className="flex items-center">
Expand All @@ -61,44 +50,6 @@ const AddressBar = () => {
<div className={mergeClasses("flex h-8 items-center", isFetching && "max-w-0 overflow-hidden opacity-0")}>
<PageDropdownInHeader />
</div>

{/* ChevronRight */}
<ChevronRight className="mx-1 h-3 w-3 flex-shrink-0 text-gray-400" />

{/* LanguageSwitcher */}
{/* <div
className={mergeClasses(
"flex h-8 items-center" + (isPartialPage ? " pr-2" : ""),
isFetching && "max-w-0 overflow-hidden opacity-0",
)}>
<LanguageSwitcher />
</div> */}

{/* ChevronRight */}
{/* <ChevronRight className="mx-1 h-3 w-3 flex-shrink-0 text-gray-400" /> */}

{/* Current page path */}
<div className="group flex items-center overflow-hidden">
<div
className={`w-full max-w-[200px] overflow-hidden overflow-ellipsis whitespace-nowrap text-xs ${isPartialPage ? "italic" : "font-mono"}`}>
{visibleSlug === "/" ? (
<span>
/<span className="text-[11px] font-light italic">(Homepage)</span>
</span>
) : (
visibleSlug
)}
</div>
{!isPartialPage && (
<Tooltip content={t("Open page")}>
<a href={fullUrl} target="_blank" rel="noopener noreferrer" className="">
<div className="ml-2 mr-px flex-shrink-0 rounded-sm p-1.5 opacity-0 transition-opacity group-hover:opacity-100">
<ExternalLink className="h-4 w-4" strokeWidth={1} />
</div>
</a>
</Tooltip>
)}
</div>
</div>

{dynamic && isDynamicPageSelectorEnabled && <DynamicPageSelectorSuspense />}
Expand Down
3 changes: 2 additions & 1 deletion src/routes/builder.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { lsThemeAtom } from "@/atoms/ui";
import { ChaiBuilderEditor, defaultChaiLibrary } from "@/core/main";
import "@/index.css";
import { lsBlocksAtom, lsDesignTokensAtom, lsThemeAtom } from "@/routes/demo/atoms-dev";
import { lsBlocksAtom, lsDesignTokensAtom } from "@/routes/demo/atoms-dev";
import { EXTERNAL_DATA } from "@/routes/demo/EXTERNAL_DATA";
import { PARTIALS } from "@/routes/demo/PARTIALS";
import { defaultShadcnPreset } from "@/routes/demo/THEME_PRESETS";
Expand Down
3 changes: 0 additions & 3 deletions src/routes/demo/atoms-dev.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { defaultThemeValues } from "@/hooks/default-theme-options";
import { ChaiTheme } from "@/types";
import { ChaiDesignTokens } from "@/types/types";
import { atomWithStorage } from "jotai/utils";

export const lsBlocksAtom = atomWithStorage("chai-builder-blocks", []);
export const lsThemeAtom = atomWithStorage<ChaiTheme>("chai-builder-theme", defaultThemeValues);
export const lsDesignTokensAtom = atomWithStorage<ChaiDesignTokens>("chai-builder-design-tokens", {});
export const lsAiContextAtom = atomWithStorage("chai-builder-ai-context", "");
3 changes: 2 additions & 1 deletion src/routes/page-preview.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { lsThemeAtom } from "@/atoms/ui";
import "@/core/index.css";
import { getChaiThemeCssVariables, getStylesForBlocks, RenderChaiBlocks } from "@/render";
import { applyDesignTokens } from "@/render/apply-design-tokens";
import { getMergedPartialBlocks } from "@/render/functions";
import { lsBlocksAtom, lsDesignTokensAtom, lsThemeAtom } from "@/routes/demo/atoms-dev";
import { lsBlocksAtom, lsDesignTokensAtom } from "@/routes/demo/atoms-dev";
import registerCustomBlocks from "@/routes/demo/blocks";
import { EXTERNAL_DATA } from "@/routes/demo/EXTERNAL_DATA";
import { PARTIALS } from "@/routes/demo/PARTIALS";
Expand Down