Skip to content
Open
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
27 changes: 27 additions & 0 deletions frontend/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,4 +249,31 @@ export default class Utils {
p[key] = val;
localStorage.setItem(prefKey, JSON.stringify(p));
};

// Source - https://stackoverflow.com/a
// Posted by Ebrahim Byagowi, modified by community. See post 'Timeline' for change history
// Retrieved 2026-01-25, License - CC BY-SA 3.0

isEqual(x, y) {
if (x === null || x === undefined || y === null || y === undefined) { return x === y; }
// after this just checking type of one would be enough
if (x.constructor !== y.constructor) { return false; }
// if they are functions, they should exactly refer to same one (because of closures)
if (x instanceof Function) { return x === y; }
// if they are regexps, they should exactly refer to same one (it is hard to better equality check on current ES)
if (x instanceof RegExp) { return x === y; }
if (x === y || x.valueOf() === y.valueOf()) { return true; }
if (Array.isArray(x) && x.length !== y.length) { return false; }

// if they are dates, they must had equal valueOf
if (x instanceof Date) { return false; }

// if they are strictly equal, they both need to be object at least
if (!(x instanceof Object)) { return false; }
if (!(y instanceof Object)) { return false; }

// recursive object equality check
const p = Object.keys(x);
return Object.keys(y).every((i) => p.indexOf(i) !== -1) && p.every((i) => this.isEqual(x[i], y[i]));
}
}
35 changes: 20 additions & 15 deletions frontend/src/views/Campaign.vue
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,13 @@
</div>
<div class="column is-6">
<b-field :label="$t('campaigns.format')" label-position="on-border" class="mr-4 mb-0">
<b-select v-model="form.content.contentType" :disabled="!canEdit || isEditing" value="richtext"
expanded>
<option v-for="(name, f) in contentTypes" :key="f" name="format" :value="f"
:data-cy="`check-${f}`">
<b-select v-model="form.content.contentType" :disabled="!canEdit || isEditing" expanded>
<!-- Empty un-selectable options as Placeholder -->
<option name="format" :value="''" :data-cy="`check-disabled`" selected disabled>
{{ $t('campaigns.formatHTML') }}
</option>
<!-- Options for formats -->
<option v-for="(name, f) in contentTypes" :key="f" name="format" :value="f" :data-cy="`check-${f}`">
{{ name }}
</option>
</b-select>
Expand Down Expand Up @@ -343,14 +346,6 @@ export default Vue.extend({

data() {
return {
contentTypes: Object.freeze({
richtext: this.$t('campaigns.richText'),
html: this.$t('campaigns.rawHTML'),
markdown: this.$t('campaigns.markdown'),
plain: this.$t('campaigns.plainText'),
visual: this.$t('campaigns.visual'),
}),

isNew: false,
isEditing: false,
isHeadersVisible: false,
Expand Down Expand Up @@ -378,7 +373,7 @@ export default Vue.extend({
tags: [],
sendAt: null,
content: {
contentType: 'richtext',
contentType: '',
body: '',
bodySource: null,
templateId: null,
Expand Down Expand Up @@ -438,8 +433,8 @@ export default Vue.extend({
},

isUnsaved() {
return this.data.body !== this.form.content.body
|| this.data.contentType !== this.form.content.contentType;
// compare changes in 'body' and 'contentType' only for content tab
return !this.$utils.isEqual(this.data.body || '', this.form.content.body) || !this.$utils.isEqual(this.data.contentType || '', this.form.content.contentType);
},

onTab(tab) {
Expand Down Expand Up @@ -730,6 +725,16 @@ export default Vue.extend({
otherMessengers() {
return this.serverConfig.messengers.filter((m) => m !== 'email' && !m.startsWith('email-'));
},
// Content types available for the selected messenger.
contentTypes() {
return {
richtext: this.$t('campaigns.richText'),
html: this.$t('campaigns.rawHTML'),
markdown: this.$t('campaigns.markdown'),
plain: this.$t('campaigns.plainText'),
visual: this.$t('campaigns.visual'),
};
},
},

beforeRouteLeave(to, from, next) {
Expand Down