Fix compatibility issues with master.

- Fix merge conflicts.
- Simply logic in campaign preview handler.
- Remove redundant `GetCampaignForPreviewWithTemplate()` and switch to the old
  query that optionally takes a template.
- Fix Vue linting issues.
This commit is contained in:
Kailash Nadh 2025-04-06 19:23:51 +05:30
parent 82e2b705bd
commit c1f81cfadd
8 changed files with 61 additions and 79 deletions

View file

@ -142,16 +142,25 @@ func (a *App) PreviewCampaign(c echo.Context) error {
return err
}
// Fetch the campaign body from the DB.
tplID, _ := strconv.Atoi(c.FormValue("template_id"))
var (
isPost = c.Request().Method == http.MethodPost
contentType = c.FormValue("content_type")
tplID, _ = strconv.Atoi(c.FormValue("template_id"))
)
// For visual content type don't use the template for preview, use only body.
if contentType == models.CampaignContentTypeVisual && tplID < 1 {
tplID = 0
}
// Get the campaign from the DB for previewing.
camp, err := a.core.GetCampaignForPreview(id, tplID)
if err != nil {
return err
}
// There's a body in the request to preview instead of the body in the DB.
if c.Request().Method == http.MethodPost {
camp.ContentType = c.FormValue("content_type")
if isPost {
camp.ContentType = contentType
camp.Body = c.FormValue("body")
}
@ -214,9 +223,6 @@ func (a *App) CreateCampaign(c echo.Context) error {
o.Type = models.CampaignTypeRegular
}
if o.ContentType == "" {
o.ContentType = models.CampaignContentTypeRichtext
}
if o.Messenger == "" {
o.Messenger = "email"
}
@ -228,7 +234,7 @@ func (a *App) CreateCampaign(c echo.Context) error {
o = c
}
if o.ArchiveTemplateID == 0 {
if o.ArchiveTemplateID.Valid && o.ArchiveTemplateID.Int != 0 {
o.ArchiveTemplateID = o.TemplateID
}

View file

@ -117,7 +117,7 @@ func (a *App) CreateTemplate(c echo.Context) error {
// Subject is only relevant for fixed tx templates. For campaigns,
// the subject changes per campaign and is on models.Campaign.
var funcs template.FuncMap
if o.Type == models.TemplateTypeCampaign {
if o.Type == models.TemplateTypeCampaign || o.Type == models.TemplateTypeCampaignVisual {
o.Subject = ""
funcs = a.manager.TemplateFuncs(nil)
} else {
@ -130,7 +130,7 @@ func (a *App) CreateTemplate(c echo.Context) error {
}
// Create the template the in the DB.
out, err := a.core.CreateTemplate(o.Name, o.Type, o.Subject, []byte(o.Body))
out, err := a.core.CreateTemplate(o.Name, o.Type, o.Subject, []byte(o.Body), o.BodySource)
if err != nil {
return err
}
@ -171,7 +171,7 @@ func (a *App) UpdateTemplate(c echo.Context) error {
// Update the template in the DB.
id := getID(c)
out, err := a.core.UpdateTemplate(id, o.Name, o.Subject, []byte(o.Body))
out, err := a.core.UpdateTemplate(id, o.Name, o.Subject, []byte(o.Body), o.BodySource)
if err != nil {
return err
}
@ -232,7 +232,7 @@ func (a *App) validateTemplate(o models.Template) error {
// previewTemplate renders the HTML preview of a template.
func (a *App) previewTemplate(tpl models.Template) ([]byte, error) {
var out []byte
if tpl.Type == models.TemplateTypeCampaign {
if tpl.Type == models.TemplateTypeCampaign || tpl.Type == models.TemplateTypeCampaignVisual {
camp := models.Campaign{
UUID: dummyUUID,
Name: a.i18n.T("templates.dummyName"),

View file

@ -46,5 +46,6 @@
"terser": "^5.34.1",
"typescript": "^5.2.2",
"vite": "^5.1.0"
}
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View file

@ -27,7 +27,8 @@
</b-select>
</b-field>
<b-field v-if="computedValue.contentType !== 'visual'" :label="$t('globals.terms.baseTemplate')" label-position="on-border">
<b-field v-if="computedValue.contentType !== 'visual'" :label="$t('globals.terms.baseTemplate')"
label-position="on-border">
<b-select :placeholder="$t('globals.terms.none')" v-model="templateId" name="template" :disabled="disabled">
<template v-for="t in applicableTemplates">
<option :value="t.id" :key="t.id">
@ -38,22 +39,25 @@
</b-field>
<div v-else>
<b-button v-if="!isVisualTplSelector" @click="onShowVisualTplSelector" type="is-ghost" icon-left="file-find-outline" data-cy="btn-select-visual-tpl">
<b-button v-if="!isVisualTplSelector" @click="onShowVisualTplSelector" type="is-ghost"
icon-left="file-find-outline" data-cy="btn-select-visual-tpl">
{{ $t('globals.terms.copyVisualTemplate') }}
</b-button>
<b-field v-else :label="$t('globals.terms.copyVisualTemplate')" label-position="on-border">
<b-select :placeholder="$t('globals.terms.none')" v-model="visualTemplateId" name="template" :disabled="disabled" class="copy-visual-template-list">
<template v-for="t in applicableTemplates">
<option :value="t.id" :key="t.id">
{{ t.name }}
</option>
</template>
</b-select>
<b-select :placeholder="$t('globals.terms.none')" v-model="visualTemplateId" name="template"
:disabled="disabled" class="copy-visual-template-list">
<template v-for="t in applicableTemplates">
<option :value="t.id" :key="t.id">
{{ t.name }}
</option>
</template>
</b-select>
<b-button :disabled="isVisualTplApplied" class="ml-3" @click="onApplyVisualTpl" type="is-primary" icon-left="content-save-outline" data-cy="btn-save-visual-tpl">
{{ $t('globals.terms.apply') }}
</b-button>
<b-button :disabled="isVisualTplApplied" class="ml-3" @click="onApplyVisualTpl" type="is-primary"
icon-left="content-save-outline" data-cy="btn-save-visual-tpl">
{{ $t('globals.terms.apply') }}
</b-button>
</b-field>
</div>
</div>
@ -68,7 +72,8 @@
<richtext-editor v-if="computedValue.contentType === 'richtext'" v-model="computedValue.body" />
<!-- visual editor //-->
<visual-editor v-if="computedValue.contentType === 'visual'" :source="computedValue.bodySource" @change="onChangeVisualEditor" height="65vh" />
<visual-editor v-if="computedValue.contentType === 'visual'" :source="computedValue.bodySource"
@change="onChangeVisualEditor" height="65vh" />
<!-- raw html editor //-->
<html-editor v-if="computedValue.contentType === 'html'" v-model="computedValue.body" />
@ -77,8 +82,8 @@
<markdown-editor v-if="computedValue.contentType === 'markdown'" v-model="computedValue.body" />
<!-- plain text //-->
<b-input v-if="computedValue.contentType === 'plain'" v-model="computedValue.body"
type="textarea" name="content" ref="plainEditor" class="plain-editor" />
<b-input v-if="computedValue.contentType === 'plain'" v-model="computedValue.body" type="textarea" name="content"
ref="plainEditor" class="plain-editor" />
<!-- campaign preview //-->
<campaign-preview v-if="isPreviewing" is-post @close="onTogglePreview" type="campaign" :id="id" :title="title"
@ -131,7 +136,7 @@ export default {
isVisualTplApplied: false,
contentType: this.$props.value.contentType,
templateId: '',
visualTemplateId: ''
visualTemplateId: '',
};
},
@ -291,7 +296,7 @@ export default {
this.isVisualTplApplied = true;
}, 250);
}
}
},
);
},
@ -302,7 +307,7 @@ export default {
const defaultTemplate = this.applicableTemplates.find((t) => t.isDefault === true);
this.templateId = defaultTemplate?.id || this.applicableTemplates[0]?.id || null;
}
}
},
},
mounted() {

View file

@ -124,27 +124,11 @@ func (c *Core) getCampaign(id int, uuid, archiveSlug string, tplType string) (mo
return out[0], nil
}
// GetCampaignForPreview retrieves a campaign with a template body.
func (c *Core) GetCampaignForPreview(id int) (models.Campaign, error) {
// GetCampaignForPreview retrieves a campaign with a template body. If the optional tplID is > 0
// that particular template is used, otherwise, the template saved on the campaign is.
func (c *Core) GetCampaignForPreview(id int, tplID int) (models.Campaign, error) {
var out models.Campaign
if err := c.q.GetCampaignForPreview.Get(&out, id); err != nil {
if err == sql.ErrNoRows {
return models.Campaign{}, echo.NewHTTPError(http.StatusBadRequest,
c.i18n.Ts("globals.messages.notFound", "name", "{globals.terms.campaign}"))
}
c.log.Printf("error fetching campaign: %v", err)
return models.Campaign{}, echo.NewHTTPError(http.StatusInternalServerError,
c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.terms.campaign}", "error", pqErrMsg(err)))
}
return out, nil
}
// GetCampaignForPreview retrieves a campaign with a template body.
func (c *Core) GetCampaignForPreviewWithTemplate(id int, tplID *int) (models.Campaign, error) {
var out models.Campaign
if err := c.q.GetCampaignForPreviewWithTemplate.Get(&out, id, tplID); err != nil {
if err := c.q.GetCampaignForPreview.Get(&out, id, tplID); err != nil {
if err == sql.ErrNoRows {
return models.Campaign{}, echo.NewHTTPError(http.StatusBadRequest,
c.i18n.Ts("globals.messages.notFound", "name", "{globals.terms.campaign}"))
@ -199,11 +183,11 @@ func (c *Core) CreateCampaign(o models.Campaign, listIDs []int, mediaIDs []int)
o.Headers,
pq.StringArray(normalizeTags(o.Tags)),
o.Messenger,
o.TemplateID.Int64,
o.TemplateID.Int,
pq.Array(listIDs),
o.Archive,
o.ArchiveSlug,
o.ArchiveTemplateID.Int64,
o.ArchiveTemplateID.Int,
o.ArchiveMeta,
pq.Array(mediaIDs),
o.BodySource,

View file

@ -234,11 +234,11 @@ type Campaign struct {
ContentType string `db:"content_type" json:"content_type"`
Tags pq.StringArray `db:"tags" json:"tags"`
Headers Headers `db:"headers" json:"headers"`
TemplateID null.Int64 `db:"template_id" json:"template_id"`
TemplateID null.Int `db:"template_id" json:"template_id"`
Messenger string `db:"messenger" json:"messenger"`
Archive bool `db:"archive" json:"archive"`
ArchiveSlug null.String `db:"archive_slug" json:"archive_slug"`
ArchiveTemplateID null.Int64 `db:"archive_template_id" json:"archive_template_id"`
ArchiveTemplateID null.Int `db:"archive_template_id" json:"archive_template_id"`
ArchiveMeta json.RawMessage `db:"archive_meta" json:"archive_meta"`
// TemplateBody is joined in from templates by the next-campaigns query.

View file

@ -58,15 +58,14 @@ type Queries struct {
UpdateListsDate *sqlx.Stmt `query:"update-lists-date"`
DeleteLists *sqlx.Stmt `query:"delete-lists"`
CreateCampaign *sqlx.Stmt `query:"create-campaign"`
QueryCampaigns string `query:"query-campaigns"`
GetCampaign *sqlx.Stmt `query:"get-campaign"`
GetCampaignForPreview *sqlx.Stmt `query:"get-campaign-for-preview"`
GetCampaignForPreviewWithTemplate *sqlx.Stmt `query:"get-campaign-for-preview-with-tpl"`
GetCampaignStats *sqlx.Stmt `query:"get-campaign-stats"`
GetCampaignStatus *sqlx.Stmt `query:"get-campaign-status"`
GetArchivedCampaigns *sqlx.Stmt `query:"get-archived-campaigns"`
CampaignHasLists *sqlx.Stmt `query:"campaign-has-lists"`
CreateCampaign *sqlx.Stmt `query:"create-campaign"`
QueryCampaigns string `query:"query-campaigns"`
GetCampaign *sqlx.Stmt `query:"get-campaign"`
GetCampaignForPreview *sqlx.Stmt `query:"get-campaign-for-preview"`
GetCampaignStats *sqlx.Stmt `query:"get-campaign-stats"`
GetCampaignStatus *sqlx.Stmt `query:"get-campaign-status"`
GetArchivedCampaigns *sqlx.Stmt `query:"get-archived-campaigns"`
CampaignHasLists *sqlx.Stmt `query:"campaign-has-lists"`
// These two queries are read as strings and based on settings.individual_tracking=on/off,
// are interpolated and copied to view and click counts. Same query, different tables.

View file

@ -663,7 +663,7 @@ LEFT JOIN bounces AS b ON (b.campaign_id = id)
ORDER BY ARRAY_POSITION($1, id);
-- name: get-campaign-for-preview
SELECT campaigns.*, COALESCE(templates.body, '') AS template_body,
SELECT campaigns.*, COALESCE(templates.body, (SELECT body FROM templates WHERE is_default = true LIMIT 1)) AS template_body,
(
SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(l)), '[]') FROM (
SELECT COALESCE(campaign_lists.list_id, 0) AS id,
@ -672,20 +672,7 @@ SELECT campaigns.*, COALESCE(templates.body, '') AS template_body,
) l
) AS lists
FROM campaigns
LEFT JOIN templates ON templates.id = campaigns.template_id
WHERE campaigns.id = $1;
-- name: get-campaign-for-preview-with-tpl
SELECT campaigns.*, COALESCE(templates.body, '') AS template_body,
(
SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(l)), '[]') FROM (
SELECT COALESCE(campaign_lists.list_id, 0) AS id,
campaign_lists.list_name AS name
FROM campaign_lists WHERE campaign_lists.campaign_id = campaigns.id
) l
) AS lists
FROM campaigns
LEFT JOIN templates ON templates.id = $2
LEFT JOIN templates ON (templates.id = (CASE WHEN $2=0 THEN campaigns.template_id ELSE $2 END))
WHERE campaigns.id = $1;
-- name: get-campaign-status