Skip to content

Commit eca2c30

Browse files
committed
Merge branch 'main' into release-14-10-2026.staging
2 parents a1b1070 + fc38f71 commit eca2c30

28 files changed

Lines changed: 405 additions & 78 deletions

‎apps/builder/app/builder/features/settings-panel/variable-menu.tsx‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,15 @@ import {
2929
} from "~/shared/sync/data-stores";
3030
import { serverSyncStore } from "~/shared/sync/sync-stores";
3131
import { readClipboardText } from "~/shared/clipboard";
32+
import {
33+
serializeVariable,
34+
deserializeVariable,
35+
} from "~/shared/copy-paste/variable-clipboard";
3236
import {
3337
DeleteDataVariableDialog,
3438
deleteDataVariable,
3539
validateDataVariableName,
3640
} from "~/builder/shared/data-variable-utils";
37-
import { serializeVariable, deserializeVariable } from "./variable-clipboard";
3841

3942
type VariableToDelete = { id: string; name: string; usages: number };
4043

‎apps/builder/app/shared/copy-paste/copy-paste.ts‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ import { webflow } from "./plugin-webflow/plugin-webflow";
1818
import { designTokens } from "./plugin-design-tokens";
1919
import { builderApi } from "../builder-api";
2020
import { readClipboardText } from "../clipboard";
21+
import { $selectedInstance } from "../nano-states";
22+
import { $dataSources, $instances, $resources } from "../sync/data-stores";
23+
import { serverSyncStore } from "../sync/sync-stores";
24+
import { findAvailableVariables } from "@webstudio-is/project-build/runtime";
25+
import { validateDataVariableName } from "~/builder/shared/data-variable-utils";
26+
import {
27+
deserializeVariable,
28+
isVariableClipboardData,
29+
} from "./variable-clipboard";
2130

2231
const isTextEditing = (event: ClipboardEvent) => {
2332
// Text is edited on the Canvas using the default Canvas text editor settings.
@@ -114,6 +123,70 @@ const reportPasteResult = (result: PasteResult) => {
114123

115124
const textMimeType = "text/plain";
116125

126+
const variableClipboard: Plugin = {
127+
name: "variable",
128+
mimeType: textMimeType,
129+
onPaste: (text) => {
130+
if (isVariableClipboardData(text) === false) {
131+
return pasteIgnored;
132+
}
133+
const instance = $selectedInstance.get();
134+
if (!instance) {
135+
return pasteHandled;
136+
}
137+
try {
138+
const copied = deserializeVariable(
139+
text,
140+
instance.id,
141+
new Map(
142+
findAvailableVariables({
143+
startingInstanceId: instance.id,
144+
instances: $instances.get(),
145+
dataSources: $dataSources.get(),
146+
}).map((variable) => [variable.id, variable])
147+
)
148+
);
149+
const originalName = copied.variable.name;
150+
let suffix = 2;
151+
let error = validateDataVariableName(
152+
copied.variable.name,
153+
undefined,
154+
instance.id
155+
);
156+
while (error?.type === "duplicate") {
157+
copied.variable.name = `${originalName} ${suffix++}`;
158+
error = validateDataVariableName(
159+
copied.variable.name,
160+
undefined,
161+
instance.id
162+
);
163+
}
164+
if (error) {
165+
return { success: false, error: error.message };
166+
}
167+
if (copied.resource) {
168+
copied.resource.name = copied.variable.name;
169+
}
170+
serverSyncStore.createTransaction(
171+
[$dataSources, $resources],
172+
(dataSources, resources) => {
173+
dataSources.set(copied.variable.id, copied.variable);
174+
if (copied.resource) {
175+
resources.set(copied.resource.id, copied.resource);
176+
}
177+
}
178+
);
179+
return pasteHandled;
180+
} catch (error) {
181+
return {
182+
success: false,
183+
error:
184+
error instanceof Error ? error.message : "Unable to paste variable",
185+
};
186+
}
187+
},
188+
};
189+
117190
const readClipboardData = (
118191
mimeTypes: Array<string>,
119192
clipboardData: null | DataTransfer
@@ -255,6 +328,7 @@ const initPlugins = ({
255328
export const initCopyPaste = ({ signal }: { signal: AbortSignal }) => {
256329
initPlugins({
257330
plugins: [
331+
variableClipboard,
258332
pageText,
259333
instanceJson,
260334
instanceText,

apps/builder/app/builder/features/settings-panel/variable-clipboard.test.ts renamed to apps/builder/app/shared/copy-paste/variable-clipboard.test.ts

File renamed without changes.

apps/builder/app/builder/features/settings-panel/variable-clipboard.ts renamed to apps/builder/app/shared/copy-paste/variable-clipboard.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ const payload = z.object({
2222
references: z.array(z.object({ id: z.string(), name: z.string() })),
2323
});
2424

25+
export const isVariableClipboardData = (text: string) => {
26+
try {
27+
return Object.hasOwn(JSON.parse(text), namespace);
28+
} catch {
29+
return false;
30+
}
31+
};
32+
2533
export const serializeVariable = (
2634
variable: DataSource,
2735
resources: ReadonlyMap<string, Resource>,

‎apps/builder/app/shared/share-project/share-project.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ const SharedLinkItem = ({
529529
role="group"
530530
aria-label={`Share link ${currentLink.name}`}
531531
>
532-
<Label css={{ flexGrow: 1 }}>{currentLink.name}</Label>
532+
<Label truncate>{currentLink.name}</Label>
533533
<CopyToClipboard
534534
text={builderUrl({
535535
authToken: currentLink.token,

‎docs/SUMMARY.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
* [🖌️ Design tokens](university/foundations/design-tokens.md)
2424
* [🎨 Style panel](university/foundations/style-panel.md)
2525
* [🎯 States and selectors](university/foundations/states-and-selectors.md)
26-
* [🔡 Data variables](university/foundations/variables.md)
26+
* [🔡 Variables](university/foundations/variables.md)
2727
* [➕ Expression editor](university/foundations/expression-editor.md)
2828
* [🎨 Custom classes & attributes](university/foundations/custom-classes-and-attributes.md)
2929
* [🔄 Transforms](university/foundations/transforms.md)

‎docs/university/core-components/head-slot.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ The Head Slot provides a visual interface for controlling your website's `<head>
1919

2020
## Key information
2121

22-
- Even though the Head Slot is visually in the Body instance, the contents of Head Slot are added to the `<head>`. This hierarchy enables [Data variables](../foundations/variables.md) defined on the Body to be used in the head.
22+
- Even though the Head Slot is visually in the Body instance, the contents of Head Slot are added to the `<head>`. This hierarchy enables [data variables](../foundations/variables.md) defined on the Body to be used in the head.
2323
- If Head Slot and Page Settings define the same data, such as meta title, Head Slot will take priority. Similarly, a default canonical is output on every page and references the current path. By specifying a canonical in Head Slot, the default canonical will not be displayed on the page.
2424
- Head Slot comes with an instance for Title, Link, and Meta. These can be duplicated. If you remove them and later need to add them back, you can copy them from a new Head Slot instance.
2525

‎docs/university/core-components/xml-node.md‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,7 @@ To include the static sitemap data in your custom sitemap, follow these steps:
3636
1. Create a page
3737
2. In the page settings, set the Document Type to XML&#x20;
3838
3. Set the page path to /sitemap.xml. This will override the default sitemap.
39-
4. Fetch the static data by clicking Create Variable > System Resource (Type) > Sitemap (Resource)&#x20;
40-
41-
<figure><img src="../../.gitbook/assets/static-sitemap-data.png" alt="Sitemap System Resource selected in Dynamic data"><figcaption></figcaption></figure>
42-
39+
4. In **Settings > Variables**, add a variable and choose **Sitemap resource** in the **New variable** dialog.
4340

4441
5. Use a [Collection](collection.md) to iterate over the static sitemap data
4542

‎docs/university/foundations/anatomy-of-the-webstudio-builder.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ The order of tokens in the Style Source Input matters. When multiple Tokens have
180180

181181
## Settings panel
182182

183-
The Settings Panel is on the right side of your canvas. You can use this panel to access and edit component-specific properties (such as ID or class) and [Data variables](variables.md) for a selected instance, enabling you to store reusable content and fetch APIs — a building block of [CMS](cms.md).
183+
The Settings Panel is on the right side of your canvas. You can use this panel to access and edit component-specific properties (such as ID or class) and [Variables](variables.md) for a selected instance, enabling you to store reusable content and fetch APIs — a building block of [CMS](cms.md).
184184

185185
---
186186

‎docs/university/foundations/cms.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ This approach is simpler — no need to conditionally show/hide content — but
194194

195195
- [Webstudio CMS](https://webstudio.is/cms) – Learn more about CMS capabilities in Webstudio
196196
- [Headless CMS Finder](https://webstudio.is/tools/headless-cms-finder) – Compare and find the right headless CMS
197-
- [Data variables](variables.md) – Define Resources to fetch CMS data
197+
- [Variables](variables.md) – Define Resources to fetch CMS data
198198
- [Expression editor](expression-editor.md) – Bind CMS data to components
199199
- [Collection](../core-components/collection.md) – Iterate over CMS data to create lists
200200
- [Content Embed](../core-components/content-embed.md) – Display rich text HTML from your CMS

0 commit comments

Comments
 (0)