feat: add custom HTML fields for portals (#233)
This commit is contained in:
parent
0fd008f843
commit
eaac65c973
@ -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
|
||||
|
||||
|
||||
@ -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')"
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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) }
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
6
db/migrate/20260309131532_add_custom_html_to_portals.rb
Normal file
6
db/migrate/20260309131532_add_custom_html_to_portals.rb
Normal 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
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user