diff --git a/innovedus_cms/base/forms.py b/innovedus_cms/base/forms.py index e32aa3f..f9ffd47 100644 --- a/innovedus_cms/base/forms.py +++ b/innovedus_cms/base/forms.py @@ -1,6 +1,4 @@ from django import forms -from django.conf import settings -from django.utils.safestring import mark_safe from wagtail.admin.rich_text import DraftailRichTextArea from .models import ContactFormSubmission, NewsletterCampaign, NewsletterTemplate @@ -33,45 +31,18 @@ class ContactForm(forms.ModelForm): } -class GrapesJSEditorWidget(forms.Textarea): +class NewsletterHtmlEditorWidget(forms.Textarea): class Media: - js = ( - "js/newsletter_template_editor.js", - ) + js = ("js/newsletter_template_editor.js",) css = { - "all": ( - "vendor/grapesjs/grapes.min.css", - "css/newsletter_template_editor.css", - ) + "all": ("css/newsletter_template_editor.css",) } def render(self, name, value, attrs=None, renderer=None): attrs = attrs or {} attrs["data-newsletter-html-input"] = "1" - attrs["data-newsletter-media-url"] = (settings.MEDIA_URL or "").strip() - attrs["hidden"] = "hidden" - textarea = super().render(name, value, attrs, renderer) - editor_shell = """ -
- """ - return mark_safe(f"{editor_shell}{textarea}") + attrs.setdefault("spellcheck", "false") + return super().render(name, value, attrs, renderer) class NewsletterCampaignEditorWidget(DraftailRichTextArea): @@ -86,12 +57,11 @@ class NewsletterCampaignEditorWidget(DraftailRichTextArea): class NewsletterTemplateAdminForm(forms.ModelForm): class Meta: model = NewsletterTemplate - fields = ["name", "subject", "template_json", "template_html", "template_text"] + fields = ["name", "subject", "template_html", "template_text"] widgets = { - "template_json": forms.HiddenInput(attrs={"data-newsletter-json-input": "1"}), - "template_html": GrapesJSEditorWidget( + "template_html": NewsletterHtmlEditorWidget( attrs={ - "rows": 18, + "rows": 28, "placeholder": "Use {{email_body}} as the content placeholder.", } ), diff --git a/innovedus_cms/base/migrations/0011_remove_newslettertemplate_template_json.py b/innovedus_cms/base/migrations/0011_remove_newslettertemplate_template_json.py new file mode 100644 index 0000000..ae292ae --- /dev/null +++ b/innovedus_cms/base/migrations/0011_remove_newslettertemplate_template_json.py @@ -0,0 +1,89 @@ +from django.db import migrations, models + + +DEFAULT_WEEKLY_ITEM_TEMPLATE = '''+ {{article_title}} +
+ +| + |
+
+ |
+ + |
+ {{article_intro}} +
+ +'
- ''
- '' - '{{article_title}}' - "" - '{{article_date}} ' - '{{article_intro}} ' - " | "
- "
+ {{article_title}} +
+ +| + |
+
+ |
+ + |
+ {{article_intro}} +
+ +您好,請點擊以下連結完成訂閱:
" - "" - ), - verbose_name=_("Subscribe HTML Template"), - ) - subscribe_text_template = models.TextField( - default="您好,請點擊以下連結完成訂閱:{{confirm_url}}", - verbose_name=_("Subscribe Text Template"), - ) - confirm_success_template = RichTextField( - blank=True, - default="訂閱確認成功。
", - verbose_name=_("Confirm Success Template"), - ) - confirm_failure_template = RichTextField( - blank=True, - default="訂閱確認失敗,請稍後再試。
", - verbose_name=_("Confirm Failure Template"), - ) - unsubscribe_intro_template = RichTextField( - blank=True, - default="確認要退訂電子報嗎?
", - verbose_name=_("Unsubscribe Intro Template"), - ) - unsubscribe_success_template = RichTextField( - blank=True, - default="已完成退訂。
", - verbose_name=_("Unsubscribe Success Template"), - ) - unsubscribe_failure_template = RichTextField( - blank=True, - default="退訂失敗,請稍後再試。
", - verbose_name=_("Unsubscribe Failure Template"), - ) - - panels = [ - MultiFieldPanel( - [ - FieldPanel("subscribe_subject_template"), - FieldPanel("subscribe_html_template"), - FieldPanel("subscribe_text_template"), - ], - heading=_("Subscribe Confirmation Email"), - ), - MultiFieldPanel( - [ - FieldPanel("confirm_success_template"), - FieldPanel("confirm_failure_template"), - FieldPanel("unsubscribe_intro_template"), - FieldPanel("unsubscribe_success_template"), - FieldPanel("unsubscribe_failure_template"), - ], - heading=_("Page Templates"), - ), - ] - - class Meta: - verbose_name = _("Newsletter Template Settings") - - @register_snippet class BannerSnippet(models.Model): key = models.CharField(max_length=50, blank=True, help_text=_("Identifier key, e.g. home / category"), verbose_name=_("Key")) diff --git a/innovedus_cms/base/newsletter.py b/innovedus_cms/base/newsletter.py index dd70e92..d5786f1 100644 --- a/innovedus_cms/base/newsletter.py +++ b/innovedus_cms/base/newsletter.py @@ -224,9 +224,8 @@ def _absolutize_links(html: str, site_base_url: str) -> str: return pattern.sub(_replace, html) -def _strip_grapesjs_svg_placeholder_images(html: str) -> str: - # GrapesJS uses inline SVG placeholder data URIs when image src is missing. - # These should not be sent as real email images. +def _strip_inline_svg_placeholder_images(html: str) -> str: + # Inline SVG data URI placeholders should not be sent as real email images. return re.sub( r"這是預覽用的假內容,實際寄送時會替換成真實內容。
' + - ''; - return (html || '') - .replace(/\{\{email_body\}\}/g, sampleBody) - .replace(/\{\{html_body\}\}/g, sampleBody) - .replace(/\{\{token\}\}/g, 'sample-token-123') - .replace(/\{\{email\}\}/g, 'demo@example.com') - .replace(/\{\{list_id\}\}/g, 'sample-list') - .replace(/\{\{tenant_id\}\}/g, 'sample-tenant') - .replace(/\{\{confirm_url\}\}/g, 'https://example.com/newsletter/confirm?token=sample-token-123') - .replace(/\{\{unsubscribe_url\}\}/g, 'https://example.com/newsletter/unsubscribe?token=sample-token-123'); + '' +
+ '本期電子報標題' + + '這是預覽用的假內容,實際寄送時會替換成文章列表。 ' + + '' + + ' |
]*>\s*<(?:ul|ol|table)\b/i.test(html || '')) { + warnings.push('Avoid wrapping ul, ol, or table directly inside p tags.'); + } + if (/\b(?:src|href)=["']\/(?!\/)/i.test(html || '')) { + warnings.push('Relative src/href detected; email templates should use absolute URLs.'); + } + return warnings; + } + + function setStatus(statusNode, messages) { + if (!statusNode) { + return; + } + if (!messages || !messages.length) { + statusNode.hidden = true; + statusNode.textContent = ''; + return; + } + statusNode.hidden = false; + statusNode.textContent = messages.join(' '); + } + + function openPreviewInFrame(frame, shell, html) { if (!frame || !shell) { return false; } @@ -198,175 +70,80 @@ return false; } doc.open(); - doc.write( - '
{{email_body}} |
{{email_body}} |
| \n |
| \n | \n |
| \n | \n | \n |
| \n | \n |
| \n |
\n Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua\n
\n "}),a('image',{label:'Image',media:"",activate:!0,content:{type:'image',style:{color:'black'}}}),a('quote',{label:'Quote',media:"",content:'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore ipsum dolor sit'}),a('link',{label:'Link',media:"",content:{type:'link',content:'Link',style:{color:'#3b97e3'}}}),a('link-block',{label:'Link Block',media:"",content:{type:'link',editable:!1,droppable:!0,style:{display:'inline-block',padding:'5px','min-height':'50px','min-width':'50px'}}});var l="
\n
| \n
| ".concat(l," | \n").concat(l," | \n
\n
| \n
\n
\n