feat: OG Image in Chatwoot Help Center (#11826)
_Note: This is only available for Cloud version of Chatwoot._ 
This commit is contained in:
@@ -17,7 +17,9 @@ class Public::Api::V1::Portals::ArticlesController < Public::Api::V1::Portals::B
|
|||||||
limit_results
|
limit_results
|
||||||
end
|
end
|
||||||
|
|
||||||
def show; end
|
def show
|
||||||
|
@og_image_url = helpers.set_og_image_url(@portal.name, @article.title)
|
||||||
|
end
|
||||||
|
|
||||||
def tracking_pixel
|
def tracking_pixel
|
||||||
@article = @portal.articles.find_by(slug: permitted_params[:article_slug])
|
@article = @portal.articles.find_by(slug: permitted_params[:article_slug])
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ class Public::Api::V1::Portals::CategoriesController < Public::Api::V1::Portals:
|
|||||||
@categories = @portal.categories.order(position: :asc)
|
@categories = @portal.categories.order(position: :asc)
|
||||||
end
|
end
|
||||||
|
|
||||||
def show; end
|
def show
|
||||||
|
@og_image_url = helpers.set_og_image_url(@portal.name, @category.name)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ class Public::Api::V1::PortalsController < Public::Api::V1::Portals::BaseControl
|
|||||||
before_action :redirect_to_portal_with_locale, only: [:show]
|
before_action :redirect_to_portal_with_locale, only: [:show]
|
||||||
layout 'portal'
|
layout 'portal'
|
||||||
|
|
||||||
def show; end
|
def show
|
||||||
|
@og_image_url = helpers.set_og_image_url('', @portal.header_text)
|
||||||
|
end
|
||||||
|
|
||||||
def sitemap
|
def sitemap
|
||||||
@help_center_url = @portal.custom_domain || ChatwootApp.help_center_root
|
@help_center_url = @portal.custom_domain || ChatwootApp.help_center_root
|
||||||
|
|||||||
@@ -1,4 +1,21 @@
|
|||||||
module PortalHelper
|
module PortalHelper
|
||||||
|
def set_og_image_url(portal_name, title)
|
||||||
|
cdn_url = GlobalConfig.get('OG_IMAGE_CDN_URL')['OG_IMAGE_CDN_URL']
|
||||||
|
return if cdn_url.blank?
|
||||||
|
|
||||||
|
client_ref = GlobalConfig.get('OG_IMAGE_CLIENT_REF')['OG_IMAGE_CLIENT_REF']
|
||||||
|
|
||||||
|
uri = URI.parse(cdn_url)
|
||||||
|
uri.path = '/og'
|
||||||
|
uri.query = URI.encode_www_form(
|
||||||
|
clientRef: client_ref,
|
||||||
|
title: title,
|
||||||
|
portalName: portal_name
|
||||||
|
)
|
||||||
|
|
||||||
|
uri.to_s
|
||||||
|
end
|
||||||
|
|
||||||
def generate_portal_bg_color(portal_color, theme)
|
def generate_portal_bg_color(portal_color, theme)
|
||||||
base_color = theme == 'dark' ? 'black' : 'white'
|
base_color = theme == 'dark' ? 'black' : 'white'
|
||||||
"color-mix(in srgb, #{portal_color} 20%, #{base_color})"
|
"color-mix(in srgb, #{portal_color} 20%, #{base_color})"
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
<% if !@is_plain_layout_enabled %>
|
<% if !@is_plain_layout_enabled %>
|
||||||
|
<% content_for :head do %>
|
||||||
|
<title><%= @portal.name %></title>
|
||||||
|
<meta name="title" content="<%= @portal.name %>">
|
||||||
|
|
||||||
|
<% if @og_image_url.present? %>
|
||||||
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
<meta name="og:image" content="<%= @og_image_url.html_safe %>">
|
||||||
|
<meta property="og:image" content="<%= @og_image_url.html_safe %>">
|
||||||
|
<meta name="twitter:image" content="<%= @og_image_url.html_safe %>">
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
<section id="portal-bg" class="w-full bg-white dark:bg-slate-900 shadow-inner">
|
<section id="portal-bg" class="w-full bg-white dark:bg-slate-900 shadow-inner">
|
||||||
<div id="portal-bg-gradient" class="pt-8 pb-8 md:pt-14 md:pb-6 min-h-[240px] md:min-h-[260px]">
|
<div id="portal-bg-gradient" class="pt-8 pb-8 md:pt-14 md:pb-6 min-h-[240px] md:min-h-[260px]">
|
||||||
<div class="mx-auto max-w-5xl px-4 md:px-8 flex flex-col items-start">
|
<div class="mx-auto max-w-5xl px-4 md:px-8 flex flex-col items-start">
|
||||||
|
|||||||
@@ -2,13 +2,23 @@
|
|||||||
<title><%= @article.title %> | <%= @portal.name %></title>
|
<title><%= @article.title %> | <%= @portal.name %></title>
|
||||||
<% if @article.meta["title"].present? %>
|
<% if @article.meta["title"].present? %>
|
||||||
<meta name="title" content="<%= @article.meta["title"] %>">
|
<meta name="title" content="<%= @article.meta["title"] %>">
|
||||||
|
<meta property="og:title" content="<%= @article.meta["title"] %>">
|
||||||
|
<meta name="twitter:title" content="<%= @article.meta["title"] %>">
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @article.meta["description"].present? %>
|
<% if @article.meta["description"].present? %>
|
||||||
<meta name="description" content="<%= @article.meta["description"] %>">
|
<meta name="description" content="<%= @article.meta["description"] %>">
|
||||||
|
<meta property="og:description" content="<%= @article.meta["description"] %>">
|
||||||
|
<meta name="twitter:description" content="<%= @article.meta["description"] %>">
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @article.meta["tags"].present? %>
|
<% if @article.meta["tags"].present? %>
|
||||||
<meta name="tags" content="<%= @article.meta["tags"].join(',') %>">
|
<meta name="tags" content="<%= @article.meta["tags"].join(',') %>">
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if @og_image_url.present? %>
|
||||||
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
<meta name="og:image" content="<%= @og_image_url.html_safe %>">
|
||||||
|
<meta property="og:image" content="<%= @og_image_url.html_safe %>">
|
||||||
|
<meta name="twitter:image" content="<%= @og_image_url.html_safe %>">
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if !@is_plain_layout_enabled %>
|
<% if !@is_plain_layout_enabled %>
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
<% content_for :head do %>
|
<% content_for :head do %>
|
||||||
<title><%= @category.name %> | <%= @portal.name %></title>
|
<title><%= @category.name %> | <%= @portal.name %></title>
|
||||||
|
<meta name="title" content="<%= @category.name %> | <%= @portal.name %>">
|
||||||
<% if @category.description.present? %>
|
<% if @category.description.present? %>
|
||||||
<meta name="description" content="<%= @category.description %>">
|
<meta name="description" content="<%= @category.description %>">
|
||||||
|
<meta property="og:description" content="<%= @category.description %>">
|
||||||
|
<meta name="twitter:description" content="<%= @category.description %>">
|
||||||
|
<% end %>
|
||||||
|
<% if @og_image_url.present? %>
|
||||||
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
<meta name="og:image" content="<%= @og_image_url.html_safe %>">
|
||||||
|
<meta property="og:image" content="<%= @og_image_url.html_safe %>">
|
||||||
|
<meta name="twitter:image" content="<%= @og_image_url.html_safe %>">
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|||||||
@@ -360,3 +360,17 @@
|
|||||||
value: 'v22.0'
|
value: 'v22.0'
|
||||||
locked: true
|
locked: true
|
||||||
# ------- End of Instagram Channel Related Config ------- #
|
# ------- End of Instagram Channel Related Config ------- #
|
||||||
|
|
||||||
|
# ------- OG Image Related Config ------- #
|
||||||
|
- name: OG_IMAGE_CDN_URL
|
||||||
|
display_title: 'OG Image CDN URL'
|
||||||
|
description: 'The CDN URL for serving OG images'
|
||||||
|
value: ''
|
||||||
|
locked: false
|
||||||
|
- name: OG_IMAGE_CLIENT_REF
|
||||||
|
display_title: 'OG Image Client Reference'
|
||||||
|
description: 'Token used to block unauthorized access to OG images'
|
||||||
|
value: ''
|
||||||
|
locked: false
|
||||||
|
type: secret
|
||||||
|
# ------- End of OG Image Related Config ------- #
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ module Enterprise::SuperAdmin::AppConfigsController
|
|||||||
|
|
||||||
def internal_config_options
|
def internal_config_options
|
||||||
%w[CHATWOOT_INBOX_TOKEN CHATWOOT_INBOX_HMAC_KEY ANALYTICS_TOKEN CLEARBIT_API_KEY DASHBOARD_SCRIPTS INACTIVE_WHATSAPP_NUMBERS BLOCKED_EMAIL_DOMAINS
|
%w[CHATWOOT_INBOX_TOKEN CHATWOOT_INBOX_HMAC_KEY ANALYTICS_TOKEN CLEARBIT_API_KEY DASHBOARD_SCRIPTS INACTIVE_WHATSAPP_NUMBERS BLOCKED_EMAIL_DOMAINS
|
||||||
CAPTAIN_CLOUD_PLAN_LIMITS ACCOUNT_SECURITY_NOTIFICATION_WEBHOOK_URL CHATWOOT_INSTANCE_ADMIN_EMAIL]
|
CAPTAIN_CLOUD_PLAN_LIMITS ACCOUNT_SECURITY_NOTIFICATION_WEBHOOK_URL CHATWOOT_INSTANCE_ADMIN_EMAIL
|
||||||
|
OG_IMAGE_CDN_URL OG_IMAGE_CLIENT_REF]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -250,12 +250,45 @@ describe PortalHelper do
|
|||||||
describe '#thumbnail_bg_color' do
|
describe '#thumbnail_bg_color' do
|
||||||
it 'returns the correct color based on username length' do
|
it 'returns the correct color based on username length' do
|
||||||
expect(helper.thumbnail_bg_color('')).to be_in(['#6D95BA', '#A4C3C3', '#E19191'])
|
expect(helper.thumbnail_bg_color('')).to be_in(['#6D95BA', '#A4C3C3', '#E19191'])
|
||||||
expect(helper.thumbnail_bg_color('Joe')).to eq('#6D95BA') # Length 3, so index is 0
|
expect(helper.thumbnail_bg_color('Joe')).to eq('#6D95BA')
|
||||||
expect(helper.thumbnail_bg_color('John')).to eq('#A4C3C3') # Length 4, so index is 1
|
expect(helper.thumbnail_bg_color('John')).to eq('#A4C3C3')
|
||||||
expect(helper.thumbnail_bg_color('Jane james')).to eq('#A4C3C3') # Length 10, so index is 1
|
expect(helper.thumbnail_bg_color('Jane james')).to eq('#A4C3C3')
|
||||||
expect(helper.thumbnail_bg_color('Jane_123')).to eq('#E19191') # Length 8, so index is 2
|
expect(helper.thumbnail_bg_color('Jane_123')).to eq('#E19191')
|
||||||
expect(helper.thumbnail_bg_color('AlexanderTheGreat')).to eq('#E19191') # Length 17, so index is 2
|
expect(helper.thumbnail_bg_color('AlexanderTheGreat')).to eq('#E19191')
|
||||||
expect(helper.thumbnail_bg_color('Reginald John Sans')).to eq('#6D95BA') # Length 18, so index is 0
|
expect(helper.thumbnail_bg_color('Reginald John Sans')).to eq('#6D95BA')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#set_og_image_url' do
|
||||||
|
let(:portal_name) { 'Chatwoot Portal' }
|
||||||
|
let(:title) { 'Welcome to Chatwoot' }
|
||||||
|
|
||||||
|
context 'when CDN URL is present' do
|
||||||
|
before do
|
||||||
|
InstallationConfig.create!(name: 'OG_IMAGE_CDN_URL', value: 'https://cdn.example.com')
|
||||||
|
InstallationConfig.create!(name: 'OG_IMAGE_CLIENT_REF', value: 'client-123')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the composed OG image URL with correct params' do
|
||||||
|
result = helper.set_og_image_url(portal_name, title)
|
||||||
|
uri = URI.parse(result)
|
||||||
|
expect(uri.path).to eq('/og')
|
||||||
|
params = Rack::Utils.parse_query(uri.query)
|
||||||
|
expect(params['clientRef']).to eq('client-123')
|
||||||
|
expect(params['title']).to eq(title)
|
||||||
|
expect(params['portalName']).to eq(portal_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when CDN URL is blank' do
|
||||||
|
before do
|
||||||
|
InstallationConfig.create!(name: 'OG_IMAGE_CDN_URL', value: '')
|
||||||
|
InstallationConfig.create!(name: 'OG_IMAGE_CLIENT_REF', value: 'client-123')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns nil' do
|
||||||
|
expect(helper.set_og_image_url(portal_name, title)).to be_nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user