Skip to content

Commit 88577d8

Browse files
authored
Merge pull request #737 from Badsender-com/fix-block-template
Fix style block template
2 parents 05fbbec + 8da3f72 commit 88577d8

File tree

7 files changed

+187
-65
lines changed

7 files changed

+187
-65
lines changed

Diff for: packages/editor/src/css/badsender-editor.less

+4
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,10 @@ body {
215215
position: absolute;
216216
top: 3px;
217217
left: 0;
218+
max-width: 100%;
219+
overflow: hidden;
220+
white-space: nowrap;
221+
text-overflow: ellipsis;
218222
}
219223

220224
.mo {

Diff for: packages/editor/src/css/style_mosaico_tools.less

+33
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,29 @@
643643
overflow: hidden;
644644
}
645645

646+
647+
.block {
648+
position: relative;
649+
}
650+
651+
.block-custom {
652+
min-height: 5rem;
653+
margin-bottom: 5px;
654+
background-color: #F3F3F3;
655+
padding-top: 35px;
656+
}
657+
658+
.block-content-custom {
659+
margin-left: 10px;
660+
661+
.block-content-custom-text {
662+
margin-top: 25px;
663+
overflow: hidden;
664+
white-space: nowrap;
665+
text-overflow: ellipsis;
666+
}
667+
}
668+
646669
#tooltabs.ui-tabs {
647670
overflow: hidden;
648671
.ui-tabs-panel {
@@ -731,6 +754,16 @@
731754
bottom: 10px;
732755
right: 5px;
733756
display: none;
757+
758+
&.custom {
759+
right: 10px;
760+
}
761+
762+
.buttons-container {
763+
.ui-button {
764+
margin: 0px;
765+
}
766+
}
734767
}
735768

736769
.image,

Diff for: packages/editor/src/js/converter/editor.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -416,8 +416,6 @@ var _propEditor = function (
416416
model._blockDescription +
417417
'</div>';
418418
}
419-
420-
421419
if (typeof prop != 'undefined') {
422420
html += '<!-- ko with: ' + prop + ' -->';
423421

@@ -611,7 +609,7 @@ var _propEditor = function (
611609
var bindings = [];
612610

613611
if (typeof globalStyleProp != 'undefined')
614-
bindings.push('css: { notnull: ' + prop + '() !== null }');
612+
bindings.push('css: { notnull: ' + prop + '() !== null && !$root.isCurrentCustomBlock() }');
615613
title =
616614
model !== null && typeof model._help !== 'undefined'
617615
? ' title="' +
@@ -663,7 +661,7 @@ var _propEditor = function (
663661

664662
if (checkboxes) {
665663
html +=
666-
'<div class="propCheck"><label data-bind="tooltips: {}"><input type="checkbox" data-bind="focusable: true, click: function(evt, obj) { $root.localGlobalSwitch(' +
664+
'<div class="propCheck"><label data-bind="tooltips: {},visible: !$root.isCurrentCustomBlock()"><input type="checkbox" data-bind="focusable: true, click: function(evt, obj) { $root.localGlobalSwitch(' +
667665
prop +
668666
', ' +
669667
globalStyleProp +

Diff for: packages/editor/src/js/template-loader.js

+19-1
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,8 @@ var templateCompiler = function (
287287

288288
delete jsorjson.tracking;
289289

290+
const mainPersonalizedBlocks = jsorjson.mainBlocks?.blocks?.filter(block => block.blockInformation);
291+
290292
// we strip content before <html> tag and after </html> because jquery doesn't parse it.
291293
// we'll keep it "raw" and use it in the preview/output methods.
292294
var res = templatecode.match(
@@ -424,7 +426,21 @@ var templateCompiler = function (
424426
undefined,
425427
content._unwrap(),
426428
blockDefs,
427-
unwrapped
429+
{
430+
...unwrapped,
431+
mainBlocks: {
432+
...unwrapped.mainBlocks,
433+
blocks: unwrapped.mainBlocks.blocks.map(item => {
434+
// We ignore blockInformation prop from blocks and don't display modal
435+
// if there is only this change between definition and current blocks
436+
// Note: if you need to ignore an other prop inside blocks, you can add the prop in the line below
437+
// For example:
438+
// const { blockInformation, myOtherPropThatIWantIgnore, anOtherPropIgnored, ...restBlock } = item;
439+
const { blockInformation, ...restBlock } = item;
440+
return restBlock;
441+
})
442+
}
443+
}
428444
)
429445
);
430446
// if checkModelRes is 1 then the model is not fully compatible but we fixed it
@@ -506,6 +522,8 @@ var templateCompiler = function (
506522
utmCampaignValue: utmCampaignValueObs,
507523
});
508524

525+
viewModel.mainPersonalizedBlocks(mainPersonalizedBlocks);
526+
509527
if (tracking && tracking.hasGoogleAnalyticsUtm) {
510528
viewModel.content().tracking().hasGoogleAnalyticsUtm(tracking.hasGoogleAnalyticsUtm);
511529
}

Diff for: packages/editor/src/js/viewmodel.js

+78-13
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ function initializeEditor(content, blockDefs, thumbPathConverter, galleryUrl) {
4747
galleryRecent: ko.observableArray([]),
4848
galleryRemote: ko.observableArray([]),
4949
selectedBlock: ko.observable(null),
50+
mainPersonalizedBlocks: ko.observable([]),
51+
isCurrentCustomBlock: ko.observable(null),
5052
selectedItem: ko.observable(null),
5153
selectedTool: ko.observable(0),
5254
selectedImageTab: ko.observable(0),
@@ -217,18 +219,73 @@ function initializeEditor(content, blockDefs, thumbPathConverter, galleryUrl) {
217219
viewModel.toggleSaveBlockModal(true, actualData, 'EDIT');
218220
};
219221

220-
// Helper function to merge blockData and templateData
222+
/**
223+
* Checks if the provided value is an object (excluding arrays).
224+
*
225+
* @param {any} value - The value to be checked.
226+
* @returns {boolean} - Returns true if the value is an object and not an array.
227+
*/
228+
function isObject(value) {
229+
return value && typeof value === 'object' && !Array.isArray(value);
230+
}
231+
232+
/**
233+
* Merges block styles with template styles. If an attribute in block styles
234+
* is undefined or null, it takes the value from the template styles. If an attribute
235+
* doesn't exist in the template styles, the original value from block styles is retained.
236+
*
237+
* @param {object} blockStyles - The styles from the block.
238+
* @param {object} templateStyles - The styles from the template.
239+
* @returns {object} - Returns the merged styles.
240+
*/
221241
function mergeBlockStylesWithTemplate(blockStyles, templateStyles) {
222-
return Object.keys(blockStyles).reduce((mergedStyles, styleKey) => {
223-
if (templateStyles.hasOwnProperty(styleKey) && styleKey !== 'type') {
224-
mergedStyles[styleKey] = templateStyles[styleKey];
242+
return Object.keys(blockStyles).reduce((mergedStyles, key) => {
243+
const blockValue = blockStyles[key];
244+
// Use the template style only if it exists, otherwise set to undefined.
245+
const templateValue = templateStyles ? templateStyles[key] : undefined;
246+
247+
// If both blockValue and templateValue are objects, merge them recursively.
248+
if (isObject(blockValue) && isObject(templateValue)) {
249+
mergedStyles[key] = mergeBlockStylesWithTemplate(blockValue, templateValue);
225250
} else {
226-
mergedStyles[styleKey] = blockStyles[styleKey];
251+
// If blockValue is undefined or null, use the templateValue, else keep the blockValue.
252+
mergedStyles[key] = blockValue === undefined || blockValue === null ? templateValue : blockValue;
227253
}
254+
228255
return mergedStyles;
229256
}, {});
230257
}
231258

259+
/**
260+
* Merges block data styles with template styles deeply. For each attribute in block data,
261+
* if it's an object, it looks through the template data to find a matching template style and merges them.
262+
* If the attribute is not an object or doesn't have a corresponding template style, it retains its original value.
263+
*
264+
* @param {object} blockData - The main block data containing styles.
265+
* @param {object} templateData - The main template data containing styles.
266+
* @returns {object} - Returns the deeply merged block styles.
267+
*/
268+
function deepMergeStylesWithTemplates(blockData, templateData) {
269+
return Object.keys(blockData).reduce((result, key) => {
270+
// If the block data attribute is an object, look for a matching template style.
271+
if (isObject(blockData[key])) {
272+
let mergedSubObject = blockData[key];
273+
for (let templateKey in templateData) {
274+
if (isObject(templateData[templateKey]) && templateData[templateKey][key]) {
275+
mergedSubObject = mergeBlockStylesWithTemplate(blockData[key], templateData[templateKey][key]);
276+
break;
277+
}
278+
}
279+
result[key] = mergedSubObject;
280+
} else {
281+
// If the block data attribute is not an object, retain its original value.
282+
result[key] = blockData[key];
283+
}
284+
285+
return result;
286+
}, {});
287+
}
288+
232289
function getTemplateData() {
233290
// gather meta
234291
// remove keys that aren't necessary to update
@@ -243,13 +300,12 @@ function initializeEditor(content, blockDefs, thumbPathConverter, galleryUrl) {
243300
// block-wysiwyg.tmpl.html
244301
viewModel.saveBlock = function (blockData) {
245302
const allTemplateData = getTemplateData();
246-
const templateContentTheme = recursivelyUnwrapObservable(allTemplateData)
247-
?.data?.theme?.contentTheme ?? {};
303+
const templateContentTheme =
304+
recursivelyUnwrapObservable(allTemplateData)?.data?.theme ?? {};
305+
248306
const unwrappedBlockData = recursivelyUnwrapObservable(blockData);
249307

250-
const finalizedBlockData = unwrappedBlockData?.customStyle
251-
? unwrappedBlockData
252-
: mergeBlockStylesWithTemplate(unwrappedBlockData, templateContentTheme);
308+
const finalizedBlockData = deepMergeStylesWithTemplates(unwrappedBlockData, templateContentTheme);
253309

254310
viewModel.toggleSaveBlockModal(true, finalizedBlockData, 'CREATE');
255311
};
@@ -312,7 +368,6 @@ function initializeEditor(content, blockDefs, thumbPathConverter, galleryUrl) {
312368
// is not the same as the default block in the template.
313369
// To fix this issue, we need to remove blockInformation when we are adding a block
314370
// in a mail.
315-
const { blockInformation, ...newBlock } = obj;
316371
// if there is a selected block we try to add the block just after the selected one.
317372
var selected = viewModel.selectedBlock();
318373
// search the selected block position.
@@ -333,14 +388,14 @@ function initializeEditor(content, blockDefs, thumbPathConverter, galleryUrl) {
333388
var pos;
334389
if (typeof found !== 'undefined') {
335390
pos = found + 1;
336-
viewModel.content().mainBlocks().blocks.splice(pos, 0, newBlock);
391+
viewModel.content().mainBlocks().blocks.splice(pos, 0, obj);
337392
viewModel.notifier.info(
338393
viewModel.t('New block added after the selected one (__pos__)', {
339394
pos: pos,
340395
})
341396
);
342397
} else {
343-
viewModel.content().mainBlocks().blocks.push(newBlock);
398+
viewModel.content().mainBlocks().blocks.push(obj);
344399
pos = viewModel.content().mainBlocks().blocks().length - 1;
345400
viewModel.notifier.info(
346401
viewModel.t('New block added at the model bottom (__pos__)', {
@@ -351,6 +406,12 @@ function initializeEditor(content, blockDefs, thumbPathConverter, galleryUrl) {
351406
// find the newly added block and select it!
352407
var added = viewModel.content().mainBlocks().blocks()[pos]();
353408
viewModel.selectBlock(added, true);
409+
410+
if (added.blockInformation()) {
411+
const blockToAdd = recursivelyUnwrapObservable(added);
412+
viewModel.mainPersonalizedBlocks([...viewModel.mainPersonalizedBlocks(), blockToAdd]);
413+
}
414+
354415
// prevent click propagation (losing url hash - see #43)
355416
return false;
356417
};
@@ -463,6 +524,10 @@ function initializeEditor(content, blockDefs, thumbPathConverter, galleryUrl) {
463524
}
464525
}.bind(viewModel, viewModel.selectedBlock);
465526

527+
viewModel.isCurrentCustomBlock = function() {
528+
return viewModel.mainPersonalizedBlocks().find(block => block.id == viewModel.selectedBlock()?.id()) != undefined;
529+
};
530+
466531
// DEBUG
467532
viewModel.countSubscriptions = function (model, debug) {
468533
var res = 0;

Diff for: packages/editor/src/tmpl-badsender/toolbox.tmpl.html

+25-23
Original file line numberDiff line numberDiff line change
@@ -63,35 +63,37 @@
6363
<div id="personalizedBlocksPlugin">
6464
<personalized-blocks-plugin />
6565
</div>
66-
<div data-bind="visible: personalizedBlocks().length > 0, foreach: personalizedBlocks()"
67-
style="text-align: center">
66+
<div data-bind="visible: personalizedBlocks().length > 0, foreach: personalizedBlocks()">
6867
<div class="draggable-item" data-bind="withProperties: { templateMode: 'show' }">
69-
<div class="block"
70-
data-bind="extdraggable: { connectClass: 'sortable-blocks-edit', data: $data, dropContainer: '#main-wysiwyg-area', dragging: $root.dragging, 'options': { handle: '.handle', distance: 10, 'appendTo': '#page' } }, click: $root.addBlock"
71-
style="position: relative;">
68+
<div class="block block-custom"
69+
data-bind="extdraggable: { connectClass: 'sortable-blocks-edit', data: $data, dropContainer: '#main-wysiwyg-area', dragging: $root.dragging, 'options': { handle: '.handle', distance: 10, 'appendTo': '#page' } }, click: $root.addBlock">
7270
<div title="Click or drag to add this block to the template" class="handle"
7371
data-bind="attr: { title: $root.t('Click or drag to add this block to the template') }, tooltips: {}">
7472
</div>
75-
<img
76-
class="block-image"
77-
data-bind="attr: { alt: $root.t('__name__', { name: ko.utils.unwrapObservable(blockInformation.name) }), src: $root.templatePath('edres/'+ko.utils.unwrapObservable(type)+'.png') }"
78-
alt="__name__" />
7973
<span class="block-name"
8074
data-bind="html: $root.t('__name__', { name: ko.utils.unwrapObservable(blockInformation.name) })">__name__</span>
81-
</div>
82-
<div class="addblockbutton">
83-
<div class="buttons-container">
84-
<div data-bind="if: $root.currentUser()?.isAdminOfCurrentGroup">
85-
<a href="javascript:void(0)" class="ui-button" data-bind="click: $root.deleteBlock">
86-
<i class="fa fa-trash"></i> <!-- Delete Icon -->
87-
</a>
88-
</div>
89-
<div data-bind="if: $root.currentUser()?.isAdminOfCurrentGroup">
90-
<a href="javascript:void(0)" class="ui-button" data-bind="click: $root.editBlock">
91-
<i class="fa fa-pencil"></i> <!-- Edit Icon -->
92-
</a>
75+
<div class="block-content-custom">
76+
<span class="block-content-custom-text"
77+
data-bind="html: $root.t('__name__', { name: ko.utils.unwrapObservable(blockInformation.category) })">__name__</span>
78+
</div>
79+
<div class="block-content-custom">
80+
<span class="block-content-custom-text"
81+
data-bind="html: $root.t('__name__', { name: ko.utils.unwrapObservable(blockInformation.userDetails.name) })">__name__</span>
82+
</div>
83+
<div class="addblockbutton custom">
84+
<div class="buttons-container">
85+
<div data-bind="if: $root.currentUser()?.isAdminOfCurrentGroup">
86+
<a href="javascript:void(0)" class="ui-button" data-bind="click: $root.deleteBlock">
87+
<i class="fa fa-trash"></i> <!-- Delete Icon -->
88+
</a>
89+
</div>
90+
<div data-bind="if: $root.currentUser()?.isAdminOfCurrentGroup">
91+
<a href="javascript:void(0)" class="ui-button" data-bind="click: $root.editBlock">
92+
<i class="fa fa-pencil"></i> <!-- Edit Icon -->
93+
</a>
94+
</div>
95+
<a href="javascript:void(0)" data-bind="click: $root.addBlock, button: { label: $root.t('Add') }">Add</a>
9396
</div>
94-
<a href="javascript:void(0)" data-bind="click: $root.addBlock, button: { label: $root.t('Add') }">Add</a>
9597
</div>
9698
</div>
9799
</div>
@@ -219,4 +221,4 @@
219221
<!-- ko block: $root.content().theme --><!-- /ko -->
220222
<!-- /ko -->
221223
<!-- /ko -->
222-
</div>
224+
</div>

0 commit comments

Comments
 (0)