Ser 2915 dymanic parameters deeplinks - #7811
Conversation
Adds a new 'dynamic-params' sidebar input type with the cly-content-dynamic-params-input component: a repeater of parameter name / user property / fallback rows used by the content editor to attach dynamic query string parameters to URL/deep-link button actions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AmXBxfgpFtGBFhDJrR7A8r
Replaces the parameter repeater with a popover matching the push
notifications Add User Property popover: internal/external property
tabs, the same cly-select-x property selector, "start with capital
letter" switch and fallback value. Confirming appends a visible
{property|fallback|c} placeholder to the URL/deeplink input.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmXBxfgpFtGBFhDJrR7A8r
Unlike push notifications, deep-link placeholders are query string
parameters, so the popover now also asks for the parameter name and
appends `name={property|fallback|c}` to the URL with the proper ?/&
separator (keeping any hash fragment at the end).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmXBxfgpFtGBFhDJrR7A8r
Two additions to the deep-link parameter editor popover:
- a "Static Value" mode next to internal/external properties, for
hardcoded parameters like param5=param5value (value URL-encoded,
served as-is)
- every query parameter in the URL is now shown as a highlighted chip
under the URL input; clicking a chip reopens the popover pre-filled
(parameter name, property/fallback/capitalize or static value) and
Confirm updates that parameter in place, while the chip's close icon
removes it from the URL
Dynamic placeholder chips are highlighted differently from static
ones, and reserved characters ({}|&=?#) are stripped from fallbacks so
they cannot break URL parsing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmXBxfgpFtGBFhDJrR7A8r
The URL/deeplink field is now a contenteditable editor that renders
every query parameter as a highlighted, non-editable token inside the
URL itself (blue for {property|fallback|c} placeholders, gray for
static values) instead of listing them under the input. Clicking a
token opens the Add User Property popover pre-filled so the parameter
can be changed or removed; the rest of the URL stays freely editable
text, with plain-text paste and single-line input enforced.
The separate plain URL input is removed — the editor takes its place
(and its "URL or deeplink" label) and edits action.value directly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmXBxfgpFtGBFhDJrR7A8r
- the Add User Property panel now floats over the canvas as a fixed-position popover (440px wide, right-aligned to the sidebar, scrollable within the viewport), so all three category tabs and the Cancel/Confirm buttons are always visible regardless of the narrow sidebar width - the URL editor keeps a static width and wraps long URLs onto multiple lines (scrolling vertically past ~5 lines) instead of scrolling horizontally Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AmXBxfgpFtGBFhDJrR7A8r
Reverts the fixed-position floating popover — the Add User Property/Edit Parameter panel opens in-flow under the button inside the sidebar, as before. To keep everything visible in the narrow pane, the category tabs wrap onto a second row when needed and the wrapping URL editor (static width, overflowing downwards) keeps the panel from being pushed off-screen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AmXBxfgpFtGBFhDJrR7A8r
The Internal/External/Static category tabs consumed too much of the narrow sidebar pane — replaced with a full-width "Value type" dropdown. The "start with capital letter" checkbox label now wraps onto multiple lines so it fits the pane. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AmXBxfgpFtGBFhDJrR7A8r
There was a problem hiding this comment.
Pull request overview
Adds dynamic parameter support for content builder deeplink URLs by introducing a dedicated sidebar input that can insert/edit query parameters as either static values or user-property placeholders.
Changes:
- Add a new
dynamic-paramssidebar input type and implementcly-content-dynamic-params-input(contenteditable URL editor + add/edit/remove param popover). - Add new dashboard localization keys for the dynamic-params UI.
- Add a changelog entry for journey_engine deeplink dynamic parameters.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| frontend/express/public/localization/dashboard/dashboard.properties | Adds i18n strings for the dynamic deeplink parameter UI. |
| frontend/express/public/javascripts/countly/vue/templates/content/UI/content-dynamic-params-input.html | New template for the dynamic-params URL editor + parameter popover. |
| frontend/express/public/javascripts/countly/vue/components/content.js | Registers new sidebar input type and implements the dynamic-params component logic. |
| CHANGELOG.md | Notes the new dynamic deeplink parameter support. |
savascountly
left a comment
There was a problem hiding this comment.
Thanks for your effort!
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (4)
frontend/express/public/javascripts/countly/vue/components/content.js:1099
openPanelForParammatches the placeholder regex against the raw query value. If the user pastes a URL where the placeholder is URL-encoded (e.g.%7Bprop%7D), the token is detected as dynamic (viasafeDecodeincreateParamNode) but clicking it opens the panel in static mode because this match fails.
this.property.key = this.safeDecode(rawKey);
const decodedValue = this.safeDecode(rawValue);
const match = decodedValue.match(CONTENT_DYNAMIC_PARAM_PLACEHOLDER_RE);
frontend/express/public/javascripts/countly/vue/components/content.js:1085
onPropertySelectis not indented consistently with neighboring methods, which hurts readability and may violate formatting rules used in this file.
onPropertySelect(value) {
const sanitized = (value === null || value === undefined) ? '' : String(value);
// reserved characters would break placeholder/query string parsing
this.property.value = sanitized.replace(/[{}|&=?#]/g, '');
},
frontend/express/public/javascripts/countly/vue/components/content.js:1065
document.execCommand('insertText', ...)is deprecated and unreliable in modern browsers. This paste handler can be implemented with Selection/Range APIs while still ensuring plain-text insertion.
const text = clipboard ? (clipboard.getData('text/plain') || '').replace(/[\r\n]+/g, '') : '';
if (text) {
document.execCommand('insertText', false, text);
}
frontend/express/public/javascripts/countly/vue/templates/content/UI/content-dynamic-params-input.html:10
- This template introduces multiple large inline
style="..."blocks. Other content-builder templates in this folder use class-based styling (e.g.content-block-list-input.html) which is easier to maintain and less likely to break theming (dark mode). Consider moving these styles into the existing stylesheet/SASS pipeline and keeping only class names here.
class="cly-vue-content-dynamic-params-input__editor"
spellcheck="false"
style="background: #FFFFFF; border: 1px solid #DBDDE1; border-radius: 4px; padding: 5px 12px; min-height: 30px; max-height: 110px; overflow-y: auto; font-size: 14px; line-height: 20px; color: #383A3F; white-space: pre-wrap; word-break: break-all; outline: none;"
:contenteditable="!disabled"
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (5)
frontend/express/public/javascripts/countly/vue/components/content.js:972
createParamNodeis mis-indented compared to the surroundingmethodsblock, which hurts readability and is likely to fail the repo's lint/format expectations for this file.
createParamNode(pair) {
const eqIndex = pair.indexOf('=');
const rawValue = eqIndex === -1 ? '' : pair.slice(eqIndex + 1);
const decodedValue = this.safeDecode(rawValue);
const isDynamic = CONTENT_DYNAMIC_PARAM_PLACEHOLDER_RE.test(decodedValue);
const node = document.createElement('span');
frontend/express/public/javascripts/countly/vue/components/content.js:1099
- The body of
openPanelForParamcontains a large unindented block, which is inconsistent with the rest of this file and makes the control flow harder to read.
this.property.key = this.safeDecode(rawKey);
const decodedValue = this.safeDecode(rawValue);
const match = decodedValue.match(CONTENT_DYNAMIC_PARAM_PLACEHOLDER_RE);
frontend/express/public/javascripts/countly/vue/components/content.js:1065
document.execCommand('insertText', ...)is deprecated and can behave inconsistently across browsers. Prefer inserting plain text viaSelection/RangeAPIs, then explicitly emitting the updated value.
onEditorPaste(event) {
// paste as plain text so foreign markup never enters the editor
event.preventDefault();
const clipboard = event.clipboardData || window.clipboardData;
const text = clipboard ? (clipboard.getData('text/plain') || '').replace(/[\r\n]+/g, '') : '';
if (text) {
document.execCommand('insertText', false, text);
}
frontend/express/public/javascripts/countly/vue/templates/content/UI/content-dynamic-params-input.html:10
- This template introduces extensive inline
style="..."usage (hard-coded colors/layout). Other templates in the same directory (e.g.content-sidebar-input.html) rely on BEM classes instead, so these styles should be moved into the existing SCSS pipeline to keep theming/maintenance consistent.
ref="editor"
class="cly-vue-content-dynamic-params-input__editor"
spellcheck="false"
style="background: #FFFFFF; border: 1px solid #DBDDE1; border-radius: 4px; padding: 5px 12px; min-height: 30px; max-height: 110px; overflow-y: auto; font-size: 14px; line-height: 20px; color: #383A3F; white-space: pre-wrap; word-break: break-all; outline: none;"
:contenteditable="!disabled"
frontend/express/public/javascripts/countly/vue/components/content.js:841
- The dynamic/static token styling is currently embedded as hard-coded CSS strings (
CONTENT_DYNAMIC_PARAM_NODE_STYLE/CONTENT_STATIC_PARAM_NODE_STYLE) and applied vianode.style.cssText. This makes theming and future UI changes harder; prefer applying a modifier class (e.g.__param--dynamic/__param--static) and styling it in SCSS.
const CONTENT_DYNAMIC_PARAM_NODE_CLASS = 'cly-vue-content-dynamic-params-input__param';
const CONTENT_DYNAMIC_PARAM_NODE_STYLE = 'background: #E1EFFF; color: #0166D6; border-radius: 3px; padding: 0 3px; cursor: pointer;';
const CONTENT_STATIC_PARAM_NODE_STYLE = 'background: #E2E4E8; color: #383A3F; border-radius: 3px; padding: 0 3px; cursor: pointer;';
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
frontend/express/public/javascripts/countly/vue/components/content.js:1048
- Clicking a query-param token identifies its index via
params.indexOf(pair), which breaks when the samename=valuepair occurs multiple times (it always edits the first match). Use the clicked node’s position among rendered param nodes instead.
const pair = paramNode.textContent || '';
const index = this.parseUrl(this.serializeEditor()).params.indexOf(pair);
if (index !== -1) {
this.openPanelForParam(index, pair);
}
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
frontend/express/public/javascripts/countly/vue/components/content.js:1048
onEditorClickfinds the clicked parameter index viaparams.indexOf(pair). If the URL contains duplicate query pairs (e.g.a=1&a=1),indexOfwill always return the first match, so clicking the second token edits/removes the wrong parameter. Use the DOM position of the clicked token instead ofindexOfto reliably identify which token was clicked.
const pair = paramNode.textContent || '';
const index = this.parseUrl(this.serializeEditor()).params.indexOf(pair);
if (index !== -1) {
this.openPanelForParam(index, pair);
}
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (2)
frontend/express/public/javascripts/countly/vue/components/content.js:1044
- Click-to-edit uses
params.indexOf(pair)to find the clicked parameter. If there are duplicatekey=valuepairs,indexOfalways returns the first match, so clicking the second duplicate opens/edits the wrong parameter. Derive the index from the clicked DOM node’s position instead of string matching.
const pair = paramNode.textContent || '';
const index = this.parseUrl(this.serializeEditor()).params.indexOf(pair);
frontend/express/public/javascripts/countly/vue/templates/content/UI/content-dynamic-params-input.html:10
- This template introduces substantial inline styling on the editor/panel elements. In this codebase, similar Vue content-builder templates typically rely on BEM classes with styles defined in
frontend/express/public/stylesheets/vue/clyvue.scss(e.g.,content-block-list-input). Keeping these styles inline makes them harder to theme and maintain. Consider moving these styles into SCSS and leaving only class names here.
<div
ref="editor"
class="cly-vue-content-dynamic-params-input__editor"
spellcheck="false"
style="background: #FFFFFF; border: 1px solid #DBDDE1; border-radius: 4px; padding: 5px 12px; min-height: 30px; max-height: 110px; overflow-y: auto; font-size: 14px; line-height: 20px; color: #383A3F; white-space: pre-wrap; word-break: break-all; outline: none;"
:contenteditable="!disabled"
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (3)
frontend/express/public/javascripts/countly/vue/templates/content/UI/content-dynamic-params-input.html:10
- This template introduces several large inline style attributes (e.g., the URL editor). In the content builder templates, styling is typically done via BEM classes (see
frontend/express/public/javascripts/countly/vue/templates/content/UI/content-sidebar-input.html:1-118) rather than inline CSS. Please move these styles into the appropriate SCSS/CSS and keep the template focused on structure.
<div
ref="editor"
class="cly-vue-content-dynamic-params-input__editor"
spellcheck="false"
style="background: #FFFFFF; border: 1px solid #DBDDE1; border-radius: 4px; padding: 5px 12px; min-height: 30px; max-height: 110px; overflow-y: auto; font-size: 14px; line-height: 20px; color: #383A3F; white-space: pre-wrap; word-break: break-all; outline: none;"
:contenteditable="!disabled"
frontend/express/public/javascripts/countly/vue/components/content.js:841
- The token styling is hard-coded as CSS strings in JS (
CONTENT_DYNAMIC_PARAM_NODE_STYLE/CONTENT_STATIC_PARAM_NODE_STYLE). For maintainability and theming consistency, consider using CSS classes/modifiers (e.g.,__param--dynamic/__param--static) and define styles in SCSS instead ofstyle.cssText.
const CONTENT_DYNAMIC_PARAM_NODE_CLASS = 'cly-vue-content-dynamic-params-input__param';
const CONTENT_DYNAMIC_PARAM_NODE_STYLE = 'background: #E1EFFF; color: #0166D6; border-radius: 3px; padding: 0 3px; cursor: pointer;';
const CONTENT_STATIC_PARAM_NODE_STYLE = 'background: #E2E4E8; color: #383A3F; border-radius: 3px; padding: 0 3px; cursor: pointer;';
frontend/express/public/javascripts/countly/vue/components/content.js:1066
onEditorPasteprevents the default paste and relies ondocument.execCommand('insertText'), which is deprecated and may fail in some browsers/environments—resulting in paste doing nothing. Add a fallback using the Selection/Range API whenexecCommandis unavailable or returns false.
onEditorPaste(event) {
// paste as plain text so foreign markup never enters the editor
event.preventDefault();
const clipboard = event.clipboardData || window.clipboardData;
const text = clipboard ? (clipboard.getData('text/plain') || '').replace(/[\r\n]+/g, '') : '';
if (text) {
document.execCommand('insertText', false, text);
}
},
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (3)
frontend/express/public/javascripts/countly/vue/components/content.js:1147
rebuildEditorshould pass the query param index intocreateParamNodeso each rendered token can keep a stabledata-indexfor click handling (important when identical pairs exist).
parsed.params.forEach((pair, index) => {
editor.appendChild(document.createTextNode(index === 0 ? '?' : '&'));
editor.appendChild(this.createParamNode(pair));
});
frontend/express/public/javascripts/countly/vue/components/content.js:1048
onEditorClickdetermines the clicked token's index usingparams.indexOf(pair), which returns the first matching pair. With duplicate query pairs (e.g.a=1&a=1), clicking the second token will open/edit the wrong one. Prefer using a stabledata-indexstored on the rendered token.
const pair = paramNode.textContent || '';
const index = this.parseUrl(this.serializeEditor()).params.indexOf(pair);
if (index !== -1) {
this.openPanelForParam(index, pair);
}
frontend/express/public/javascripts/countly/vue/components/content.js:977
createParamNodedoesn't persist the query parameter's position, so click handling has to fall back toindexOf(pair)(which breaks with duplicates). Store the index on the created node so it can be read back on click.
This issue also appears on line 1144 of the same file.
createParamNode(pair) {
const eqIndex = pair.indexOf('=');
const rawValue = eqIndex === -1 ? '' : pair.slice(eqIndex + 1);
const decodedValue = this.safeDecode(rawValue);
const isDynamic = CONTENT_DYNAMIC_PARAM_PLACEHOLDER_RE.test(decodedValue);
| catch (error) { | ||
| return value; | ||
| } | ||
| } | ||
|
|
||
| serializeEditor() { |
No description provided.