feat: add custom HTML fields for portals (#233)

This commit is contained in:
Gabriel Jablonski 2026-03-09 11:47:41 -03:00 committed by GitHub
parent 0fd008f843
commit eaac65c973
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 97 additions and 2 deletions

View File

@ -79,7 +79,8 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
def portal_params
params.require(:portal).permit(
:id, :color, :custom_domain, :header_text, :homepage_link,
:name, :page_title, :slug, :archived, { config: [:default_locale, :show_author, { allowed_locales: [] }] }
:name, :page_title, :slug, :archived, :custom_head_html, :custom_body_html,
{ config: [:default_locale, :show_author, { allowed_locales: [] }] }
)
end

View File

@ -47,6 +47,8 @@ const state = reactive({
logoUrl: '',
avatarBlobId: '',
showAuthor: true,
customHeadHtml: '',
customBodyHtml: '',
});
const originalState = reactive({ ...state });
@ -120,6 +122,8 @@ watch(
slug: newVal.slug,
liveChatWidgetInboxId: newVal.inbox?.id || '',
showAuthor: newVal.config?.show_author !== false,
customHeadHtml: newVal.custom_head_html || '',
customBodyHtml: newVal.custom_body_html || '',
});
if (newVal.logo) {
const {
@ -153,6 +157,8 @@ const handleUpdatePortal = () => {
blob_id: state.avatarBlobId,
inbox_id: state.liveChatWidgetInboxId,
config: { show_author: state.showAuthor },
custom_head_html: state.customHeadHtml,
custom_body_html: state.customBodyHtml,
};
emit('updatePortal', portal);
};
@ -354,6 +360,54 @@ const handleAvatarDelete = () => {
</span>
</div>
</div>
<div
class="grid items-start justify-between w-full gap-2 grid-cols-[200px,1fr]"
>
<label
class="text-sm font-medium whitespace-nowrap py-2.5 text-n-slate-12"
>
{{ t('HELP_CENTER.PORTAL_SETTINGS.FORM.CUSTOM_HEAD_HTML.LABEL') }}
</label>
<div class="flex flex-col gap-1">
<textarea
v-model="state.customHeadHtml"
:placeholder="
t('HELP_CENTER.PORTAL_SETTINGS.FORM.CUSTOM_HEAD_HTML.PLACEHOLDER')
"
rows="4"
class="w-full px-3 py-2 text-sm border rounded-lg resize-y border-n-weak bg-transparent dark:bg-transparent text-n-slate-12 placeholder:text-n-slate-9 focus:outline-none focus:ring-1 focus:ring-n-brand font-mono"
/>
<span class="text-xs text-n-slate-11">
{{
t('HELP_CENTER.PORTAL_SETTINGS.FORM.CUSTOM_HEAD_HTML.HELP_TEXT')
}}
</span>
</div>
</div>
<div
class="grid items-start justify-between w-full gap-2 grid-cols-[200px,1fr]"
>
<label
class="text-sm font-medium whitespace-nowrap py-2.5 text-n-slate-12"
>
{{ t('HELP_CENTER.PORTAL_SETTINGS.FORM.CUSTOM_BODY_HTML.LABEL') }}
</label>
<div class="flex flex-col gap-1">
<textarea
v-model="state.customBodyHtml"
:placeholder="
t('HELP_CENTER.PORTAL_SETTINGS.FORM.CUSTOM_BODY_HTML.PLACEHOLDER')
"
rows="4"
class="w-full px-3 py-2 text-sm border rounded-lg resize-y border-n-weak bg-transparent dark:bg-transparent text-n-slate-12 placeholder:text-n-slate-9 focus:outline-none focus:ring-1 focus:ring-n-brand font-mono"
/>
<span class="text-xs text-n-slate-11">
{{
t('HELP_CENTER.PORTAL_SETTINGS.FORM.CUSTOM_BODY_HTML.HELP_TEXT')
}}
</span>
</div>
</div>
<div class="flex justify-end w-full gap-2">
<Button
:label="t('HELP_CENTER.PORTAL_SETTINGS.FORM.SAVE_CHANGES')"

View File

@ -751,6 +751,16 @@
"LABEL": "Show article authors",
"HELP_TEXT": "Display author information on category pages"
},
"CUSTOM_HEAD_HTML": {
"LABEL": "Custom head HTML",
"PLACEHOLDER": "Add custom HTML to the <head> section (e.g., meta tags, analytics, custom CSS)",
"HELP_TEXT": "This code will be injected into the <head> of your portal pages"
},
"CUSTOM_BODY_HTML": {
"LABEL": "Custom body HTML",
"PLACEHOLDER": "Add custom HTML before </body> (e.g., scripts, tracking pixels, widgets)",
"HELP_TEXT": "This code will be injected before the closing </body> tag of your portal pages"
},
"SAVE_CHANGES": "Save changes"
},
"CONFIGURATION_FORM": {

View File

@ -751,6 +751,16 @@
"LABEL": "Mostrar autores dos artigos",
"HELP_TEXT": "Exibir informações de autoria nas páginas de categorias"
},
"CUSTOM_HEAD_HTML": {
"LABEL": "HTML personalizado no head",
"PLACEHOLDER": "Adicione HTML personalizado na seção <head> (ex: meta tags, analytics, CSS personalizado)",
"HELP_TEXT": "Este código será injetado no <head> das páginas do seu portal"
},
"CUSTOM_BODY_HTML": {
"LABEL": "HTML personalizado no body",
"PLACEHOLDER": "Adicione HTML personalizado antes do </body> (ex: scripts, pixels de rastreamento, widgets)",
"HELP_TEXT": "Este código será injetado antes da tag de fechamento </body> das páginas do seu portal"
},
"SAVE_CHANGES": "Salvar Alterações"
},
"CONFIGURATION_FORM": {

View File

@ -6,7 +6,9 @@
# archived :boolean default(FALSE)
# color :string
# config :jsonb
# custom_body_html :text
# custom_domain :string
# custom_head_html :text
# header_text :text
# homepage_link :string
# name :string not null
@ -40,6 +42,8 @@ class Portal < ApplicationRecord
validates :name, presence: true
validates :slug, presence: true, uniqueness: true
validates :custom_domain, uniqueness: true, allow_nil: true
validates :custom_head_html, length: { maximum: 15_000 }
validates :custom_body_html, length: { maximum: 15_000 }
validate :config_json_format
scope :active, -> { where(archived: false) }

View File

@ -8,6 +8,8 @@ json.page_title portal.page_title
json.slug portal.slug
json.archived portal.archived
json.account_id portal.account_id
json.custom_head_html portal.custom_head_html
json.custom_body_html portal.custom_body_html
json.config do
json.allowed_locales do

View File

@ -57,6 +57,9 @@ By default, it renders:
document.documentElement.classList.add('light')
}
</script>
<% if !@is_plain_layout_enabled && @portal.custom_head_html.present? %>
<%= @portal.custom_head_html.html_safe %>
<% end %>
</head>
<body>
<div id="portal" class="antialiased">
@ -157,4 +160,7 @@ By default, it renders:
<% if @portal.channel_web_widget.present? && !@is_plain_layout_enabled %>
<%= @portal.channel_web_widget.web_widget_script.html_safe %>
<% end %>
<% if !@is_plain_layout_enabled && @portal.custom_body_html.present? %>
<%= @portal.custom_body_html.html_safe %>
<% end %>
</html>

View File

@ -0,0 +1,6 @@
class AddCustomHtmlToPortals < ActiveRecord::Migration[7.1]
def change
add_column :portals, :custom_head_html, :text
add_column :portals, :custom_body_html, :text
end
end

View File

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2026_02_26_194714) do
ActiveRecord::Schema[7.1].define(version: 2026_03_09_131532) do
# These extensions should be enabled to support this database
enable_extension "pg_stat_statements"
enable_extension "pg_trgm"
@ -1095,6 +1095,8 @@ ActiveRecord::Schema[7.1].define(version: 2026_02_26_194714) do
t.boolean "archived", default: false
t.bigint "channel_web_widget_id"
t.jsonb "ssl_settings", default: {}, null: false
t.text "custom_head_html"
t.text "custom_body_html"
t.index ["channel_web_widget_id"], name: "index_portals_on_channel_web_widget_id"
t.index ["custom_domain"], name: "index_portals_on_custom_domain", unique: true
t.index ["slug"], name: "index_portals_on_slug", unique: true