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
2 changes: 2 additions & 0 deletions apps/builder/app/builder/builder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { TooltipProvider } from "@radix-ui/react-tooltip";
import {
theme,
Box,
Toaster,
type CSS,
Flex,
Grid,
Expand Down Expand Up @@ -480,6 +481,7 @@ export const Builder = ({
<KeyboardShortcutsDialog />
<TokenConflictDialog />
<RemoteDialog />
<Toaster />
</div>
</TooltipProvider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const buildBreakpointFromEditorState = (
newBreakpoint.condition = originalBreakpoint.condition;
} else {
// Invalid: no condition and no valid width
return undefined;
return;
}

return newBreakpoint;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const ConfirmationDialog = ({
return (
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && onAbort()}>
<DialogContent>
<DialogTitle>Delete Breakpoint</DialogTitle>
<DialogTitle>Delete breakpoint</DialogTitle>
<Flex gap="2" direction="column" css={{ padding: theme.spacing[5] }}>
<Text>{`Are you sure you want to delete "${breakpoint.label}"?`}</Text>
<Text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const DeletePageConfirmationDialog = ({
}}
>
<DialogContent>
<DialogTitle>Delete Page</DialogTitle>
<DialogTitle>Delete page</DialogTitle>
<Flex gap="3" direction="column" css={{ padding: theme.panel.padding }}>
<Text>{`Are you sure you want to delete "${page.name}"?`}</Text>
<Text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ const MetadataItem = (props: {
export const CustomMetadata = (props: CustomMetadataProps) => {
return (
<Grid gap={2} css={{ my: theme.spacing[5], mx: theme.spacing[8] }}>
<Label text="title">Custom Metadata</Label>
<Label text="title">Custom metadata</Label>
<Text color="subtle">
Use this section to input metadata for the document, which will be used
to generate{" "}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ const FormFields = ({
<ScrollArea>
<Grid gap={3} css={{ padding: theme.panel.padding }}>
<Grid gap={1}>
<Label htmlFor={fieldIds.name}>Folder Name</Label>
<Label htmlFor={fieldIds.name}>Folder name</Label>
<InputErrorsTooltip errors={errors.name}>
<InputField
tabIndex={1}
Expand Down
80 changes: 69 additions & 11 deletions apps/builder/app/builder/features/pages/page-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import {
$userPlanFeatures,
$isDesignMode,
} from "~/shared/nano-states";
import { $openProjectSettings } from "~/shared/nano-states/project-settings";
import {
BindingControl,
BindingPopover,
Expand Down Expand Up @@ -106,6 +107,11 @@ import {
} from "./page-utils";
import { Form } from "./form";
import { CustomMetadata } from "./custom-metadata";
import { findMatchingRedirect } from "~/shared/project-settings/utils";
import {
LOOP_ERROR,
wouldCreateLoop,
} from "~/shared/redirects/redirect-loop-detection";

const fieldDefaultValues = {
name: "Untitled",
Expand Down Expand Up @@ -250,6 +256,24 @@ const validateValues = (
errors.path.push(...messages);
}
}

// Validate redirect doesn't create a loop
if (
pages !== undefined &&
values.path !== undefined &&
computedValues.redirect &&
typeof computedValues.redirect === "string" &&
computedValues.redirect !== ""
) {
const existingRedirects = pages.redirects ?? [];
if (
wouldCreateLoop(values.path, computedValues.redirect, existingRedirects)
) {
errors.redirect = errors.redirect ?? [];
errors.redirect.push(LOOP_ERROR);
}
}

return errors;
};

Expand Down Expand Up @@ -368,7 +392,7 @@ const StatusField = ({
return (
<Grid gap={1}>
<Flex align="center" gap={1}>
<Label htmlFor={id}>Status Code </Label>
<Label htmlFor={id}>Status code </Label>
<Tooltip
content={
<Text>
Expand Down Expand Up @@ -524,14 +548,24 @@ const LanguageField = ({
);
};

const usePageUrl = (values: Values) => {
const pages = useStore($pages);
const foldersPath =
pages === undefined ? "" : getPagePath(values.parentFolderId, pages);
const path = [foldersPath, values.path]
/**
* Compute the full page path from form values.
* This combines folder path with page path, handling home page special case.
*/
const computePagePath = (values: Values, pages: Pages): string => {
if (values.isHomePage) {
return "/";
}
const foldersPath = getPagePath(values.parentFolderId, pages);
return [foldersPath, values.path]
.filter(Boolean)
.join("/")
.replace(/\/+/g, "/");
};

const usePageUrl = (values: Values) => {
const pages = useStore($pages);
const path = pages === undefined ? "" : computePagePath(values, pages);

const system = useStore($currentSystem);
const publishedOrigin = useStore($publishedOrigin);
Expand Down Expand Up @@ -621,7 +655,7 @@ const MarketplaceSection = ({
/>
)}
<Grid gap={1}>
<Label>Marketplace Preview</Label>
<Label>Marketplace preview</Label>
<Box
css={{
padding: theme.spacing[5],
Expand Down Expand Up @@ -683,15 +717,39 @@ const FormFields = ({
computeExpression(values.excludePageFromSearch, variableValues)
);

// Check if any redirect matches this page's path
const fullPagePath = computePagePath(values, pages);
const matchingRedirect = findMatchingRedirect(
fullPagePath,
pages.redirects ?? []
);

return (
<Grid css={{ height: "100%" }}>
<ScrollArea>
{matchingRedirect && (
<PanelBanner variant="warning">
<Text>
A redirect from "{matchingRedirect.old}" will override this page.
The page will not be rendered when published.{" "}
<Link
color="inherit"
underline="always"
onClick={() => {
$openProjectSettings.set("redirects");
}}
>
Go to Redirects settings
</Link>
</Text>
</PanelBanner>
)}
{/**
* ----------------------========<<<Page props>>>>========----------------------
*/}
<Grid gap={2} css={{ padding: theme.panel.padding }}>
<Grid gap={1}>
<Label htmlFor={fieldIds.name}>Page Name</Label>
<Label htmlFor={fieldIds.name}>Page name</Label>
<InputErrorsTooltip errors={errors.name}>
<InputField
color={errors.name && "error"}
Expand Down Expand Up @@ -814,7 +872,7 @@ const FormFields = ({
)}

<Grid gap={1}>
<Label htmlFor={fieldIds.documentType}>Document Type</Label>
<Label htmlFor={fieldIds.documentType}>Document type</Label>
<Select
options={documentTypes}
getValue={(docType: (typeof documentTypes)[number]) => docType}
Expand Down Expand Up @@ -850,7 +908,7 @@ const FormFields = ({
pages.
</Text>
<Grid gap={1}>
<Label>Search Result Preview</Label>
<Label>Search result preview</Label>
<Box
css={{
padding: theme.spacing[5],
Expand Down Expand Up @@ -1038,7 +1096,7 @@ const FormFields = ({
<Text color="subtle">
This image appears when you share a link to this page on social
media sites. If no image is set here, the Social Image set in the
Project Settings will be used. The optimal dimensions for the
project settings will be used. The optimal dimensions for the
image are 1200x630 px or larger with a 1.91:1 aspect ratio.
</Text>
<BindingControl>
Expand Down
2 changes: 1 addition & 1 deletion apps/builder/app/builder/features/pages/social-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const SocialPreview = ({
}: SocialPreviewProps) => {
return (
<Grid gap={1}>
<Label>Social Sharing Preview</Label>
<Label>Social sharing preview</Label>

<Grid
gap={1}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ export const CodeControl = ({
}
>
<Flex gap="1" align="center">
<Text variant="labels">Code Editor</Text>
<Text variant="labels">Code editor</Text>
{errorInfo}
</Flex>
</DialogTitle>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export const TextContent = ({
</DialogTitleActions>
}
>
<Text variant="labels">Text Content</Text>
<Text variant="labels">Text content</Text>
</DialogTitle>
}
size="small"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ export const SearchParams = ({
return (
<Grid gap={1}>
<Flex justify="between" align="center">
<Label>Search Params</Label>
<Label>Search params</Label>
<SmallIconButton
aria-label="Add another search param"
icon={<PlusIcon />}
Expand Down Expand Up @@ -495,7 +495,7 @@ const CacheMaxAge = ({
}) => {
return (
<Grid gap={1}>
<Label htmlFor="resource-panel-max-age">Cache Max Age</Label>
<Label htmlFor="resource-panel-max-age">Cache max age</Label>
<InputField
id="resource-panel-max-age"
suffix={
Expand Down Expand Up @@ -1225,7 +1225,7 @@ export const GraphqlResourceForm = forwardRef<
</Grid>

<Grid gap={1}>
<Label>GraphQL Variables</Label>
<Label>GraphQL variables</Label>
{/* use invisible text input to reflect expression editor in form
type=hidden does not emit invalid event */}
<input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,21 @@ export const BackgroundImage = ({ index }: { index: number }) => {
const newValue = parsed.get("background-image");

if (newValue === undefined || newValue?.type === "invalid") {
return undefined;
return;
}

const [layer] = newValue.type === "layers" ? newValue.value : [newValue];

// Only validate image URLs, not keywords or other types
if (layer?.type !== "image" || layer.value.type !== "url") {
return undefined;
return;
}

const url = layer.value.url;

// If it's an absolute URL, no validation needed
if (isAbsoluteUrl(url)) {
return undefined;
return;
}

// Check if the asset exists in the project
Expand All @@ -42,7 +42,7 @@ export const BackgroundImage = ({ index }: { index: number }) => {
return [`Asset ${url} is not found in project`];
}

return undefined;
return;
},
[]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ export const StyleSourceInput = (
const { key, ...itemProps } = getItemProps({ item, index });
return (
<Fragment key={index}>
<ComboboxLabel>New Token</ComboboxLabel>
<ComboboxLabel>New token</ComboboxLabel>
<ComboboxListboxItem
{...itemProps}
key={key}
Expand All @@ -509,7 +509,7 @@ export const StyleSourceInput = (
label = (
<>
{hasNewTokenItem && <ComboboxSeparator />}
<ComboboxLabel>Global Tokens</ComboboxLabel>
<ComboboxLabel>Global tokens</ComboboxLabel>
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const DomainCheckbox = (props: DomainCheckboxProps) => {

const tooltipContentForFreeUsers = allowStagingPublish ? undefined : (
<Flex direction="column" gap="2" css={{ maxWidth: theme.spacing[28] }}>
<Text variant="titles">Publish to Staging</Text>
<Text variant="titles">Publish to staging</Text>
<Text>
<Flex direction="column">
Staging allows you to preview a production version of your site
Expand Down
19 changes: 8 additions & 11 deletions apps/builder/app/builder/features/workspace/workspace.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useRef, type ReactNode } from "react";
import { useStore } from "@nanostores/react";
import { theme, Toaster, css } from "@webstudio-is/design-system";
import { theme, css } from "@webstudio-is/design-system";
import {
$canvasWidth,
$scale,
Expand Down Expand Up @@ -131,15 +131,12 @@ export const CanvasToolsContainer = () => {
const outlineStyle = useOutlineStyle();

return (
<>
<div
data-name="canvas-tools-wrapper"
className={canvasContainerStyle()}
style={outlineStyle}
>
<CanvasTools />
</div>
<Toaster />
</>
<div
data-name="canvas-tools-wrapper"
className={canvasContainerStyle()}
style={outlineStyle}
>
<CanvasTools />
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ export const AssetInfo = ({ asset }: { asset: Asset }) => {
/>
</PopoverTrigger>
<PopoverContent css={{ minWidth: 250 }}>
<PopoverTitle>Asset Details</PopoverTitle>
<PopoverTitle>Asset details</PopoverTitle>
<AssetInfoContent asset={asset} usages={usages} />
</PopoverContent>
</Popover>
Expand Down
2 changes: 1 addition & 1 deletion apps/builder/app/builder/shared/binding-popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ const BindingPanel = ({
</CssValueListArrowFocus>
</Box>
<Flex gap="1" css={{ padding: theme.panel.padding }}>
<Text variant="labels">Expression Editor</Text>
<Text variant="labels">Expression editor</Text>
<Tooltip
variant="wrapped"
content={
Expand Down
2 changes: 1 addition & 1 deletion apps/builder/app/builder/shared/css-variable-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ export const renameCssVariable = (
}
});

return undefined;
return;
};

export const deleteUnusedCssVariables = () => {
Expand Down
Loading